From 10a9a2e86db118cd4b638de5aaeb5fa1243f74fe Mon Sep 17 00:00:00 2001 From: Marko Ristin-Kaufmann Date: Thu, 15 Oct 2026 19:00:22 +0200 Subject: [PATCH] Make read-only sequences of verifications generic We make the read-only ``Sequence[...]`` arguments of the verification functions generic if their items are instances of a class with descendants. This way, the sequences of the descendants can be passed without a copy, as aas-core-meta V3.2 does, *e.g.*, in ``ID_shorts_are_unique(self.submodel_elements)``. Go declares a type parameter, *e.g.*, ``func IDShortsAreUnique[ ReferablesT ourtypes.IReferable](referables []ReferablesT)``, C++ a template defined in the header, C# the covariant ``IReadOnlyList``, and Java the wildcard ``List``. Python and TypeScript accept the sequences of the descendants already. The methods stay invariant, since Go methods can not have type parameters and C++ virtual methods can not be templates, and so do the mutable ``List[...]`` arguments. The type inference now checks the items of all the list arguments, and restricts the usages of the generic sequences, *e.g.*, they can not be passed to a method. We support ``in`` on the generic sequences with the new ``ourcommon.SliceContainsInstance`` in Go, and the immutable aliases, *e.g.*, ``alias: Final[Sequence[Item]] = items``, which C# and Java now declare with ``var``. We test all this in the new common meta-model ``variance_of_containers``. --- .../cpp/lib/_generate_verification.py | 730 +- aas_core_codegen/cpp/transpilation.py | 159 +- .../csharp/lib/_generate_verification.py | 48 +- aas_core_codegen/csharp/transpilation.py | 13 + .../golang/lib/_generate_common.py | 18 + .../golang/lib/_generate_verification.py | 105 +- aas_core_codegen/golang/transpilation.py | 102 +- aas_core_codegen/intermediate/__init__.py | 1 + aas_core_codegen/intermediate/_types.py | 45 + .../intermediate/type_inference.py | 266 +- .../java/lib/_generate_verification.py | 49 +- aas_core_codegen/java/transpilation.py | 14 + .../variance_of_containers.py | 407 + .../expected_symbol_table.txt | 1637 ++ .../include/dummy/verification.hpp | 73 +- .../expected_output/src/verification.cpp | 36 - .../include/dummy/verification.hpp | 108 +- .../expected_output/src/verification.cpp | 57 - .../include/dummy/verification.hpp | 41 +- .../expected_output/src/verification.cpp | 18 - .../expected_output/include/dummy/common.hpp | 10196 +++++++++++ .../include/dummy/constants.hpp | 38 + .../include/dummy/enhancing.hpp | 1104 ++ .../include/dummy/iteration.hpp | 372 + .../include/dummy/jsonization.hpp | 272 + .../expected_output/include/dummy/pattern.hpp | 34 + .../expected_output/include/dummy/revm.hpp | 213 + .../include/dummy/stringification.hpp | 91 + .../expected_output/include/dummy/types.hpp | 512 + .../include/dummy/verification.hpp | 884 + .../include/dummy/visitation.hpp | 103 + .../include/dummy/wstringification.hpp | 63 + .../include/dummy/xml_path.hpp | 136 + .../include/dummy/xmlization.hpp | 245 + .../expected_output/src/common.cpp | 14232 ++++++++++++++++ .../expected_output/src/constants.cpp | 25 + .../expected_output/src/iteration.cpp | 951 ++ .../expected_output/src/jsonization.cpp | 2917 ++++ .../expected_output/src/pattern.cpp | 15 + .../expected_output/src/revm.cpp | 861 + .../expected_output/src/stringification.cpp | 375 + .../expected_output/src/types.cpp | 375 + .../expected_output/src/verification.cpp | 1246 ++ .../expected_output/src/visitation.cpp | 186 + .../expected_output/src/wstringification.cpp | 94 + .../expected_output/src/xml_common.cpp | 1624 ++ .../expected_output/src/xml_common.hpp | 676 + .../expected_output/src/xml_path.cpp | 239 + .../expected_output/src/xmlization.cpp | 3185 ++++ .../expected_output/stdout.txt | 1 + .../expected_output/test/catch_main.cpp | 12 + .../expected_output/test/common.cpp | 422 + .../expected_output/test/common.hpp | 142 + .../expected_output/test/common_examples.cpp | 335 + .../expected_output/test/common_examples.hpp | 88 + .../test/common_jsonization.cpp | 93 + .../test/common_jsonization.hpp | 42 + .../test/common_xmlization.cpp | 637 + .../test/common_xmlization.hpp | 50 + .../test/test_descent_and_descent_once.cpp | 264 + .../test/test_jsonization_dispatch.cpp | 125 + .../test_jsonization_of_concrete_classes.cpp | 467 + .../expected_output/test/test_revm.cpp | 286 + .../test/test_string_helpers.cpp | 349 + .../test/test_stringification_base64.cpp | 337 + .../test/test_stringification_of_enums.cpp | 84 + .../test/test_verification.cpp | 324 + .../test/test_wstringification_of_enums.cpp | 84 + .../test/test_x_or_default.cpp | 24 + .../test/test_xmlization_dispatch.cpp | 149 + .../test_xmlization_of_concrete_classes.cpp | 349 + .../input/snippets/namespace.txt | 1 + .../expected_output/return_code.json | 1 + .../expected_output/stderr.txt | 2 + .../expected_output/stdout.txt | 0 .../input/snippets/namespace.txt | 1 + .../meta_model.py | 50 + .../expected_output/dummy/verification.cs | 18 +- .../expected_output/dummy/verification.cs | 15 +- .../expected_output/dummy/verification.cs | 7 +- .../expected_output/dummy.Tests/Common.cs | 156 + .../expected_output/dummy.Tests/CommonJson.cs | 222 + .../dummy.Tests/CommonJsonization.cs | 158 + .../dummy.Tests/TestCopying.cs | 335 + .../TestDescendAndVisitorThrough.cs | 201 + .../dummy.Tests/TestDescendOnce.cs | 125 + .../dummy.Tests/TestEnhancing.cs | 168 + .../TestJsonizationOfConcreteClasses.cs | 798 + .../dummy.Tests/TestJsonizationOfEnums.cs | 23 + .../TestJsonizationOfInterfaces.cs | 135 + .../dummy.Tests/TestOverXOrEmpty.cs | 40 + .../dummy.Tests/TestStringHelpers.cs | 411 + .../dummy.Tests/TestVerificationOfEnums.cs | 22 + .../dummy.Tests/TestXOrDefault.cs | 72 + .../dummy.Tests/TestXmlizationErrors.cs | 156 + .../TestXmlizationOfConcreteClasses.cs | 703 + .../dummy.Tests/TestXmlizationOfInterfaces.cs | 251 + .../expected_output/dummy/common.cs | 199 + .../expected_output/dummy/constants.cs | 22 + .../expected_output/dummy/copying.cs | 178 + .../expected_output/dummy/enhancing.cs | 565 + .../expected_output/dummy/jsonization.cs | 1287 ++ .../expected_output/dummy/reporting.cs | 275 + .../expected_output/dummy/stringification.cs | 22 + .../expected_output/dummy/types.cs | 578 + .../expected_output/dummy/verification.cs | 725 + .../expected_output/dummy/visitation.cs | 311 + .../expected_output/dummy/xmlcommon.cs | 319 + .../expected_output/dummy/xmlization.cs | 1599 ++ .../expected_output/stdout.txt | 1 + .../input/snippets/namespace.txt | 1 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../verification/verification.go | 14 +- .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../enum/expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../verification/verification.go | 24 +- .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../verification/verification.go | 12 +- .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../sets/expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../expected_output/common/common.go | 16 + .../tuples/expected_output/common/common.go | 16 + .../unions/expected_output/common/common.go | 16 + .../expected_output/common/common.go | 350 + .../string_helpers_test.go | 347 + .../expected_output/constants/constants.go | 11 + .../expected_output/enhancing/enhancing.go | 478 + .../enhancing/test/enhancing_test.go | 295 + .../internal/xmlcommon/xmlcommon.go | 1062 ++ .../jsonization/jsonization.go | 1156 ++ .../test/classes_with_descendants_test.go | 130 + .../jsonization/test/common_test.go | 189 + .../jsonization/test/concrete_classes_test.go | 521 + .../jsonization/test/enums_test.go | 11 + .../ourtesting/common_jsonization.go | 350 + .../expected_output/ourtesting/constants.go | 43 + .../expected_output/ourtesting/deep_equal.go | 239 + .../expected_output/ourtesting/doc.go | 6 + .../expected_output/ourtesting/filesystem.go | 69 + .../expected_output/ourtesting/tracing.go | 20 + .../expected_output/reporting/reporting.go | 240 + .../expected_output/stdout.txt | 1 + .../stringification/stringification.go | 74 + .../types/descend_test/common.go | 96 + .../types/descend_test/descend_once_test.go | 109 + .../types/descend_test/descend_test.go | 109 + .../types/is_xxx_test/is_xxx_test.go | 157 + .../expected_output/types/types.go | 704 + .../xxx_or_default_test.go | 153 + .../verification/test/verification_test.go | 692 + .../verification/verification.go | 863 + .../xmlization/test/common_test.go | 296 + .../xmlization/test/concrete_classes_test.go | 582 + .../expected_output/xmlization/xmlization.go | 1221 ++ .../input/snippets/repo_url.txt | 1 + .../expected_output/common/common.go | 16 + .../java/dummy/verification/Verification.java | 16 +- .../java/dummy/verification/Verification.java | 12 +- .../java/dummy/verification/Verification.java | 4 +- .../main/java/dummy/common/StringHelpers.java | 142 + .../src/main/java/dummy/common/Tuple1.java | 23 + .../src/main/java/dummy/common/Tuple2.java | 23 + .../src/main/java/dummy/common/Tuple3.java | 23 + .../src/main/java/dummy/common/Tuple4.java | 23 + .../src/main/java/dummy/common/Tuple5.java | 23 + .../src/main/java/dummy/common/Tuple6.java | 23 + .../src/main/java/dummy/common/Tuple7.java | 23 + .../src/main/java/dummy/common/Tuple8.java | 23 + .../main/java/dummy/constants/Constants.java | 37 + .../src/main/java/dummy/copying/Copying.java | 175 + .../main/java/dummy/enhancing/Enhanced.java | 23 + .../dummy/enhancing/EnhancedContainer.java | 110 + .../dummy/enhancing/EnhancedSomeItem.java | 87 + .../dummy/enhancing/EnhancedSpecialItem.java | 97 + .../java/dummy/enhancing/EnhancedWrapper.java | 77 + .../main/java/dummy/enhancing/Enhancer.java | 64 + .../main/java/dummy/enhancing/Unwrapper.java | 56 + .../main/java/dummy/enhancing/_Wrapper.java | 162 + .../dummy/generation/ContainerBuilder.java | 43 + .../dummy/generation/SomeItemBuilder.java | 35 + .../dummy/generation/SpecialItemBuilder.java | 43 + .../java/dummy/jsonization/Jsonization.java | 1050 ++ .../main/java/dummy/reporting/Reporting.java | 345 + .../stringification/Stringification.java | 22 + .../main/java/dummy/types/enums/IEnum.java | 24 + .../main/java/dummy/types/impl/Container.java | 261 + .../main/java/dummy/types/impl/Descent.java | 84 + .../main/java/dummy/types/impl/SomeItem.java | 120 + .../java/dummy/types/impl/SpecialItem.java | 146 + .../main/java/dummy/types/impl/Wrapper.java | 176 + .../main/java/dummy/types/model/IClass.java | 61 + .../java/dummy/types/model/IContainer.java | 47 + .../main/java/dummy/types/model/IItem.java | 29 + .../java/dummy/types/model/ISomeItem.java | 22 + .../java/dummy/types/model/ISpecialItem.java | 25 + .../main/java/dummy/types/model/IWrapper.java | 25 + .../java/dummy/verification/Verification.java | 709 + .../dummy/visitation/AbstractTransformer.java | 44 + .../AbstractTransformerWithContext.java | 54 + .../dummy/visitation/AbstractVisitor.java | 37 + .../AbstractVisitorWithContext.java | 45 + .../java/dummy/visitation/ITransformer.java | 39 + .../visitation/ITransformerWithContext.java | 44 + .../main/java/dummy/visitation/IVisitor.java | 37 + .../dummy/visitation/IVisitorWithContext.java | 43 + .../java/dummy/visitation/VisitorThrough.java | 63 + .../main/java/dummy/xmlcommon/XmlCommon.java | 586 + .../java/dummy/xmlization/Xmlization.java | 1255 ++ .../expected_output/src/test/java/Common.java | 150 + .../src/test/java/CommonJson.java | 98 + .../src/test/java/CommonJsonization.java | 129 + .../src/test/java/TestCopying.java | 265 + .../java/TestDescendAndVisitorThrough.java | 166 + .../src/test/java/TestDescendOnce.java | 105 + .../src/test/java/TestEnhancing.java | 169 + .../TestJsonizationOfConcreteClasses.java | 527 + .../src/test/java/TestJsonizationOfEnums.java | 25 + .../java/TestJsonizationOfInterfaces.java | 91 + .../src/test/java/TestOverXOrEmpty.java | 37 + .../src/test/java/TestStringHelpers.java | 364 + .../test/java/TestVerificationOfEnums.java | 26 + .../src/test/java/TestXOrDefault.java | 53 + .../java/TestXmlizationOfConcreteClasses.java | 647 + .../test/java/TestXmlizationOfInterfaces.java | 154 + .../expected_output/stdout.txt | 1 + .../input/snippets/package.txt | 1 + .../expected_output/schema.json | 133 + .../expected_output/stdout.txt | 1 + .../input/snippets/schema_base.json | 10 + .../expected_output/dev/tests/common.py | 138 + .../dev/tests/common_jsonization.py | 197 + .../dev/tests/common_xmlization.py | 186 + .../test_descend_and_pass_through_visitor.py | 112 + .../dev/tests/test_descend_once.py | 49 + .../dev/tests/test_for_over_X_or_empty.py | 64 + .../dev/tests/test_for_x_or_default.py | 27 + ...jsonization_of_classes_with_descendants.py | 86 + .../test_jsonization_of_concrete_classes.py | 166 + .../dev/tests/test_jsonization_of_enums.py | 26 + .../dev/tests/test_len_slicing_and_find.py | 310 + .../dev/tests/test_verification.py | 295 + ..._xmlization_of_classes_with_descendants.py | 133 + .../test_xmlization_of_concrete_classes.py | 314 + .../expected_output/dummy/common.py | 87 + .../expected_output/dummy/constants.py | 17 + .../expected_output/dummy/jsonization.py | 1034 ++ .../expected_output/dummy/reporting.py | 191 + .../expected_output/dummy/stringification.py | 17 + .../expected_output/dummy/types.py | 905 + .../expected_output/dummy/verification.py | 617 + .../expected_output/dummy/xmlcommon.py | 759 + .../expected_output/dummy/xmlization.py | 2476 +++ .../expected_output/stdout.txt | 1 + .../input/snippets/qualified_module_name.txt | 1 + .../expected_output/src/common.ts | 604 + .../expected_output/src/constants.ts | 15 + .../expected_output/src/index.ts | 23 + .../expected_output/src/jsonization.ts | 1459 ++ .../expected_output/src/stringification.ts | 88 + .../expected_output/src/types.ts | 1448 ++ .../expected_output/src/verification.ts | 818 + .../expected_output/src/xmlcommon.ts | 1045 ++ .../expected_output/src/xmlization.ts | 1274 ++ .../expected_output/stdout.txt | 1 + .../test/common.base64.spec.ts | 212 + .../test/common.base64url.spec.ts | 145 + .../test/common.stringHelpers.spec.ts | 205 + .../expected_output/test/common.ts | 380 + .../expected_output/test/commonJsonization.ts | 272 + .../expected_output/test/commonXmlization.ts | 273 + .../test/jsonization.common.spec.ts | 31 + .../test/jsonization.concreteClasses.spec.ts | 764 + .../test/jsonization.enums.spec.ts | 12 + .../test/jsonization.interfaces.spec.ts | 155 + .../test/jsonization.negative.spec.ts | 77 + .../test/stringification.enums.spec.ts | 12 + .../expected_output/test/types.casts.spec.ts | 172 + ...types.descendAndPassThroughVisitor.spec.ts | 178 + .../test/types.descendOnce.spec.ts | 115 + .../test/types.modelType.spec.ts | 148 + .../test/types.overEnum.spec.ts | 11 + .../test/types.overXOrEmpty.spec.ts | 37 + .../test/types.typeMatches.spec.ts | 140 + .../test/types.xOrDefault.spec.ts | 88 + .../expected_output/test/verification.spec.ts | 49 + .../test/xmlization.basic.spec.ts | 68 + .../test/xmlization.common.spec.ts | 31 + .../test/xmlization.concreteClasses.spec.ts | 306 + .../test/xmlization.enums.spec.ts | 14 + .../test/xmlization.interfaces.spec.ts | 166 + .../test/xmlization.negative.spec.ts | 133 + .../input/snippets/package_documentation.txt | 1 + .../input/snippets/package_identifier.txt | 1 + .../expected_output/schema.xsd | 88 + .../expected_output/stdout.txt | 1 + .../input/snippets/root_element.xml | 8 + .../expected_symbol_table.txt | 2252 +++ dev/tests/intermediate/test_type_inference.py | 360 +- dev/tests/test_main.py | 54 + 327 files changed, 102626 insertions(+), 469 deletions(-) create mode 100644 dev/test_data/common_meta_models/variance_of_containers.py create mode 100644 dev/test_data/intermediate/common_meta_models/variance_of_containers/expected_symbol_table.txt create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/common.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/constants.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/enhancing.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/iteration.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/jsonization.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/pattern.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/revm.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/stringification.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/types.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/verification.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/visitation.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/wstringification.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xml_path.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xmlization.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/common.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/constants.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/iteration.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/jsonization.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/pattern.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/revm.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/stringification.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/types.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/verification.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/visitation.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/wstringification.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_path.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xmlization.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/catch_main.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.hpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_descent_and_descent_once.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_dispatch.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_of_concrete_classes.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_revm.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_string_helpers.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_base64.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_of_enums.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_verification.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_wstringification_of_enums.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_x_or_default.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_dispatch.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_of_concrete_classes.cpp create mode 100644 dev/test_data/main/cpp/expected/variance_of_containers/input/snippets/namespace.txt create mode 100644 dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/return_code.json create mode 100644 dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stderr.txt create mode 100644 dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stdout.txt create mode 100644 dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/input/snippets/namespace.txt create mode 100644 dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/meta_model.py create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/Common.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJson.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJsonization.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestCopying.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendAndVisitorThrough.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendOnce.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestEnhancing.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfConcreteClasses.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfEnums.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfInterfaces.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestOverXOrEmpty.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestStringHelpers.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestVerificationOfEnums.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXOrDefault.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationErrors.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfConcreteClasses.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfInterfaces.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/common.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/constants.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/copying.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/enhancing.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/jsonization.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/reporting.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/stringification.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/types.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/verification.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/visitation.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlcommon.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlization.cs create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/csharp/expected/variance_of_containers/input/snippets/namespace.txt create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/common.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/string_helpers_test/string_helpers_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/constants/constants.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/enhancing.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/test/enhancing_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/internal/xmlcommon/xmlcommon.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/jsonization.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/classes_with_descendants_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/common_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/concrete_classes_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/enums_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/common_jsonization.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/constants.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/deep_equal.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/doc.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/filesystem.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/tracing.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/reporting/reporting.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/stringification/stringification.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/common.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_once_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/is_xxx_test/is_xxx_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/types.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/xxx_or_default_test/xxx_or_default_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/test/verification_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/verification.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/common_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/concrete_classes_test.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/xmlization.go create mode 100644 dev/test_data/main/golang/expected/variance_of_containers/input/snippets/repo_url.txt create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/StringHelpers.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple1.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple2.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple3.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple4.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple5.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple6.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple7.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple8.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/constants/Constants.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/copying/Copying.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhanced.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedContainer.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSomeItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSpecialItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedWrapper.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhancer.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Unwrapper.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/_Wrapper.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/ContainerBuilder.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SomeItemBuilder.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SpecialItemBuilder.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/jsonization/Jsonization.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/reporting/Reporting.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/stringification/Stringification.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/enums/IEnum.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Container.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Descent.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SomeItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SpecialItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Wrapper.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IClass.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IContainer.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISomeItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISpecialItem.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IWrapper.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/verification/Verification.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformer.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformerWithContext.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitor.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitorWithContext.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformer.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformerWithContext.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitor.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitorWithContext.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/VisitorThrough.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlcommon/XmlCommon.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlization/Xmlization.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/Common.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJson.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJsonization.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestCopying.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendAndVisitorThrough.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendOnce.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestEnhancing.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfConcreteClasses.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfEnums.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfInterfaces.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestOverXOrEmpty.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestStringHelpers.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestVerificationOfEnums.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXOrDefault.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfConcreteClasses.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfInterfaces.java create mode 100644 dev/test_data/main/java/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/java/expected/variance_of_containers/input/snippets/package.txt create mode 100644 dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/schema.json create mode 100644 dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/jsonschema/expected/variance_of_containers/input/snippets/schema_base.json create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_jsonization.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_xmlization.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_and_pass_through_visitor.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_once.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_over_X_or_empty.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_x_or_default.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_classes_with_descendants.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_concrete_classes.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_enums.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_len_slicing_and_find.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_verification.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_classes_with_descendants.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_concrete_classes.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/common.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/constants.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/jsonization.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/reporting.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/stringification.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/types.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/verification.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlcommon.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlization.py create mode 100644 dev/test_data/main/python/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/python/expected/variance_of_containers/input/snippets/qualified_module_name.txt create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/common.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/constants.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/index.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/jsonization.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/stringification.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/types.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/verification.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlcommon.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlization.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64url.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.stringHelpers.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonJsonization.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonXmlization.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.common.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.concreteClasses.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.enums.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.interfaces.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.negative.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/stringification.enums.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.casts.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendAndPassThroughVisitor.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendOnce.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.modelType.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overEnum.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overXOrEmpty.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.typeMatches.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.xOrDefault.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/verification.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.basic.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.common.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.concreteClasses.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.enums.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.interfaces.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.negative.spec.ts create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_documentation.txt create mode 100644 dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_identifier.txt create mode 100644 dev/test_data/main/xsd/expected/variance_of_containers/expected_output/schema.xsd create mode 100644 dev/test_data/main/xsd/expected/variance_of_containers/expected_output/stdout.txt create mode 100644 dev/test_data/main/xsd/expected/variance_of_containers/input/snippets/root_element.xml create mode 100644 dev/test_data/parse/common_meta_models/variance_of_containers/expected_symbol_table.txt diff --git a/aas_core_codegen/cpp/lib/_generate_verification.py b/aas_core_codegen/cpp/lib/_generate_verification.py index c4162e149..df7af7b7b 100644 --- a/aas_core_codegen/cpp/lib/_generate_verification.py +++ b/aas_core_codegen/cpp/lib/_generate_verification.py @@ -11,6 +11,7 @@ Mapping, Set, Final, + Dict, ) from icontract import ensure, require @@ -57,6 +58,109 @@ def _generate_return_type(verification: intermediate.Verification) -> Stripped: ) +def _type_parameter_name(argument: intermediate.Argument) -> Identifier: + """ + Generate the name of the template parameter for the items of the ``argument``. + + For example, ``items`` gives ``ItemsT``. + """ + return Identifier(f"{cpp_naming.class_name(argument.name)}T") + + +def _is_generic(verification: intermediate.VerificationUnion) -> bool: + """ + Check whether the transpiled ``verification`` function is a template. + + The verification function is a template if it has one or more generic + sequences, see :py:func:`intermediate.generic_sequence_item_class`. + The implementation-specific verification functions declare their templates + in their snippets. + """ + return isinstance(verification, intermediate.TranspilableVerification) and any( + intermediate.generic_sequence_item_class(arg) is not None + for arg in verification.arguments + ) + + +def _generate_signature( + verification: Union[ + intermediate.TranspilableVerification, + intermediate.PatternVerification, + ], +) -> Stripped: + """ + Generate the signature of the ``verification`` function. + + The ``std::vector`` is invariant in C++, so we declare a template parameter + for the items of each generic sequence, see + :py:func:`intermediate.generic_sequence_item_class`. This way, the vectors of + the descendants can be passed without a copy. The virtual methods can not be + templates in C++, so only the verification functions have generic sequences. + + The template parameters are constrained by ``static_assert``'s in the body + instead of ``std::enable_if`` in the signature so that the template can be + declared and defined separately. + """ + type_parameters = [] # type: List[Identifier] + arg_definitions = [] # type: List[Stripped] + for arg in verification.arguments: + arg_name = cpp_naming.argument_name(arg.name) + + if intermediate.generic_sequence_item_class(arg) is None: + arg_type = cpp_common.generate_argument_type( + argument=arg, types_namespace=cpp_common.TYPES_NAMESPACE + ) + else: + type_parameter = _type_parameter_name(arg) + type_parameters.append(type_parameter) + + vector_type = Stripped( + f"""\ +std::vector< +{I}std::shared_ptr<{type_parameter}> +>""" + ) + if isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation): + arg_type = Stripped( + f"""\ +const common::optional< +{I}{indent_but_first_line(vector_type, I)} +>&""" + ) + else: + arg_type = Stripped(f"const {vector_type}&") + + arg_definitions.append(Stripped(f"{arg_type} {arg_name}")) + + function_name = cpp_naming.function_name(verification.name) + return_type = _generate_return_type(verification) + + if len(arg_definitions) == 0: + signature = Stripped(f"{return_type} {function_name}()") + else: + arg_definitions_joined = ",\n".join(arg_definitions) + signature = Stripped( + f"""\ +{return_type} {function_name}( +{I}{indent_but_first_line(arg_definitions_joined, I)} +)""" + ) + + if len(type_parameters) == 0: + return signature + + type_parameters_joined = ",\n".join( + f"typename {type_parameter}" for type_parameter in type_parameters + ) + return Stripped( + f"""\ +template< +{I}{indent_but_first_line(type_parameters_joined, I)} +> +{signature}""" + ) + + @ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) def _generate_verification_function_definition( verification: Union[ @@ -84,20 +188,21 @@ def _generate_verification_function_definition( return code, None - arg_types_names = [ - ( - cpp_common.generate_argument_type( - argument=arg, types_namespace=cpp_common.TYPES_NAMESPACE - ), - cpp_naming.argument_name(arg.name), - ) - for arg in verification.arguments - ] + remarks_on_generic_sequences = [] # type: List[str] + for arg in verification.arguments: + item_cls = intermediate.generic_sequence_item_class(arg) + if item_cls is None: + continue - function_name = cpp_naming.function_name(verification.name) - arg_definitions_joined = ",\n".join( - f"{arg_type} {arg_name}" for arg_type, arg_name in arg_types_names - ) + interface_name = ( + f"{cpp_common.TYPES_NAMESPACE}::{cpp_naming.interface_name(item_cls.name)}" + ) + remarks_on_generic_sequences.append( + f"\\tparam {_type_parameter_name(arg)} type of the items of " + f"\\p {cpp_naming.argument_name(arg.name)}, {interface_name} or one of " + f"its descendants, so that the vectors of the descendants can be passed " + f"without a copy, as std::vector is invariant" + ) blocks = [] # type: List[Stripped] if verification.description is not None: @@ -106,6 +211,7 @@ def _generate_verification_function_definition( context=cpp_description.Context( namespace=cpp_common.VERIFICATION_NAMESPACE, cls_or_enum=None ), + extra_remarks=remarks_on_generic_sequences, ) if errors is not None: return None, Error( @@ -116,20 +222,16 @@ def _generate_verification_function_definition( ) assert comment is not None blocks.append(comment) - - return_type = _generate_return_type(verification) - - if len(arg_types_names) == 0: - signature = Stripped(f"{return_type} {function_name}()") - else: - signature = Stripped( - f"""\ -{return_type} {function_name}( -{I}{indent_but_first_line(arg_definitions_joined, I)} -)""" + elif len(remarks_on_generic_sequences) > 0: + blocks.append( + cpp_description.documentation_comment( + Stripped("\n\n".join(remarks_on_generic_sequences)) + ) ) + else: + pass - blocks.append(Stripped(f"{signature};")) + blocks.append(Stripped(f"{_generate_signature(verification)};")) return Stripped("\n".join(blocks)), None @@ -171,12 +273,274 @@ def _generate_definition_of_verify_constrained_primitive( */""" ) - return Stripped( - f"""\ -{documentation_comment} -std::unique_ptr {verify_name}( -{I}{arg_type} {arg_name} + return Stripped( + f"""\ +{documentation_comment} +std::unique_ptr {verify_name}( +{I}{arg_type} {arg_name} +);""" + ) + + +def _check_no_calls_to_internal_verifications( + verification: intermediate.TranspilableVerification, + symbol_table: intermediate.SymbolTable, +) -> List[Error]: + """ + Check that the public generic ``verification`` calls no internal verification. + + The public generic verification functions are templates defined in the header, + see :py:func:`_is_generic`, while the internal verification functions are + declared only in the implementation file. + """ + errors = [] # type: List[Error] + for node_in_body in verification.parsed.body: + for node in parse_tree.over_nodes(node_in_body): + if not isinstance(node, parse_tree.FunctionCall): + continue + + callee = symbol_table.verification_functions_by_name.get( + node.name.identifier, None + ) + if ( + callee is None + or callee.visibility is not intermediate.Visibility.INTERNAL + ): + continue + + errors.append( + Error( + node.original_node, + f"The public verification function {verification.name!r} has " + f"generic sequences, so we generate it in C++ as a template " + f"defined in the header. However, it calls the internal " + f"verification function {callee.name!r}, which is declared " + f"only in the implementation file. Please make " + f"{callee.name!r} public, or {verification.name!r} internal.", + ) + ) + + return errors + + +class _TranspilableVerificationTranspiler(cpp_transpilation.Transpiler): + """Transpile the body of a :class:`TranspilableVerification`.""" + + # fmt: off + @require( + lambda environment, verification: + all( + environment.find(arg.name) is not None + for arg in verification.arguments + ), + "All arguments defined in the environment" + ) + # fmt: on + def __init__( + self, + type_map: Mapping[ + parse_tree.Node, intermediate_type_inference.TypeAnnotationUnion + ], + is_optional_map: Mapping[parse_tree.Node, bool], + downcast_map: Mapping[parse_tree.Node, intermediate_type_inference.Downcast], + is_optional_before_downcast_map: Mapping[parse_tree.Node, bool], + environment: intermediate_type_inference.Environment, + symbol_table: intermediate.SymbolTable, + verification: intermediate.TranspilableVerification, + aliasing: cpp_aliasing.Aliasing, + ) -> None: + """Initialize with the given values.""" + cpp_transpilation.Transpiler.__init__( + self, + type_map=type_map, + is_optional_map=is_optional_map, + downcast_map=downcast_map, + is_optional_before_downcast_map=is_optional_before_downcast_map, + environment=environment, + types_namespace=cpp_common.TYPES_NAMESPACE, + aliasing=aliasing, + ) + + self._symbol_table = symbol_table + + self._argument_name_set = frozenset(arg.name for arg in verification.arguments) + + # NOTE (mristin): + # The type inference propagates the very type annotation of the items of + # an argument to the loop variables, the generator variables and + # the indexed items, so we can tell by the identity of the type annotation + # that a value is of the template parameter of a generic sequence. + type_parameter_by_items_id = dict() # type: Dict[int, Identifier] + for arg in verification.arguments: + if intermediate.generic_sequence_item_class(arg) is None: + continue + + arg_type_anno = environment.find(arg.name) + assert arg_type_anno is not None + + list_type_anno = intermediate_type_inference.beneath_optional(arg_type_anno) + assert isinstance( + list_type_anno, intermediate_type_inference.ListTypeAnnotation + ) + + type_parameter_by_items_id[id(list_type_anno.items)] = _type_parameter_name( + arg + ) + + self._type_parameter_by_items_id = type_parameter_by_items_id + + def _type_parameter_of( + self, type_annotation: intermediate_type_inference.TypeAnnotationUnion + ) -> Optional[Identifier]: + return self._type_parameter_by_items_id.get(id(type_annotation), None) + + def transform_name( + self, node: parse_tree.Name + ) -> Tuple[Optional[Stripped], Optional[Error]]: + if node.identifier in self._variable_name_set: + return Stripped(cpp_naming.variable_name(node.identifier)), None + + if node.identifier in self._argument_name_set: + return Stripped(cpp_naming.argument_name(node.identifier)), None + + if node.identifier in self._symbol_table.constants_by_name: + constant = cpp_naming.constant_name(node.identifier) + return Stripped(f"{cpp_common.CONSTANTS_NAMESPACE}::{constant}"), None + + if node.identifier in self._symbol_table.verification_functions_by_name: + return Stripped(cpp_naming.function_name(node.identifier)), None + + our_type = self._symbol_table.find_our_type(name=node.identifier) + if isinstance(our_type, intermediate.Enumeration): + return ( + Stripped( + f"{cpp_common.TYPES_NAMESPACE}::{cpp_naming.enum_name(node.identifier)}" + ), + None, + ) + + return None, Error( + node.original_node, + f"We can not determine how to transpile the name {node.identifier!r} " + f"to C++. We could not find it neither in the constants, nor in " + f"verification functions, nor as an enumeration. " + f"If you expect this name to be transpilable, please contact " + f"the developers.", + ) + + +@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) +def _generate_implementation_of_transpilable_verification( + verification: intermediate.TranspilableVerification, + symbol_table: intermediate.SymbolTable, + base_environment: intermediate_type_inference.Environment, +) -> Tuple[Optional[Stripped], Optional[Error]]: + """Transpile the verification to a function implementation.""" + # fmt: off + type_inference, inference_error = ( + intermediate_type_inference.infer_for_verification( + verification=verification, + base_environment=base_environment + ) + ) + # fmt: on + + if inference_error is not None: + return None, inference_error + + assert type_inference is not None + + aliasing, aliasing_errors = cpp_aliasing.analyze( + body=verification.parsed.body, + arguments=verification.arguments, + type_map=type_inference.type_map, + binds_self=False, + ) + if aliasing_errors is not None: + return None, Error( + verification.parsed.node, + f"Failed to transpile the verification function {verification.name!r} " + f"so that its C++ variables alias the values as in Python", + aliasing_errors, + ) + + assert aliasing is not None + + optional_inferrer = cpp_optionaling.Inferrer( + environment=type_inference.environment_with_args, + type_map=type_inference.type_map, + downcast_map=type_inference.downcast_map, + ) + for node in verification.parsed.body: + _ = optional_inferrer.transform(node) + + if len(optional_inferrer.errors) > 0: + return None, Error( + verification.parsed.node, + f"Failed to infer whether one or more nodes are ``common::optional`` " + f"in the verification function {verification.name!r}", + optional_inferrer.errors, + ) + + transpiler = _TranspilableVerificationTranspiler( + type_map=type_inference.type_map, + is_optional_map=optional_inferrer.is_optional_map, + downcast_map=type_inference.downcast_map, + is_optional_before_downcast_map=( + optional_inferrer.is_optional_before_downcast_map + ), + environment=type_inference.environment_with_args, + symbol_table=symbol_table, + verification=verification, + aliasing=aliasing, + ) + + body = [] # type: List[Stripped] + for node in verification.parsed.body: + stmt, error = transpiler.transform(node) + if error is not None: + return None, Error( + verification.parsed.node, + f"Failed to transpile the verification function {verification.name!r}", + [error], + ) + + assert stmt is not None + body.append(stmt) + + # NOTE (mristin): + # We constrain the template parameters of the generic sequences here instead + # of in the signature, see :py:func:`_generate_signature`. + static_asserts = [] # type: List[Stripped] + for arg in verification.arguments: + item_cls = intermediate.generic_sequence_item_class(arg) + if item_cls is None: + continue + + interface_name = ( + f"{cpp_common.TYPES_NAMESPACE}::{cpp_naming.interface_name(item_cls.name)}" + ) + arg_name = cpp_naming.argument_name(arg.name) + static_asserts.append( + Stripped( + f"""\ +static_assert( +{I}std::is_base_of<{interface_name}, {_type_parameter_name(arg)}>::value, +{I}"Expected the items of {arg_name} to be instances of {interface_name}" );""" + ) + ) + + body_joined = "\n".join([*static_asserts, *body]) + + return ( + Stripped( + f"""\ +{_generate_signature(verification)} {{ +{I}{indent_but_first_line(body_joined, I)} +}}""" + ), + None, ) @@ -198,14 +562,55 @@ def generate_header( include_guard_var = cpp_common.include_guard_var(namespace) - unordered_set_include = ( - "#include \n" - if intermediate_uses.sets_in(symbol_table.verification_functions) - else "" - ) - include_prefix_path = cpp_common.generate_include_prefix_path(library_namespace) + # NOTE (mristin): + # The public generic verification functions are templates, so we define them + # in the header, see :py:func:`_is_generic`. Their bodies need the same + # includes as the definitions in the implementation file. + public_generic_verifications = [ + verification + for verification in symbol_table.verification_functions + if ( + verification.visibility is intermediate.Visibility.PUBLIC + and _is_generic(verification) + ) + ] + + our_includes = [ + f'#include "{include_prefix_path}/common.hpp"', + f'#include "{include_prefix_path}/iteration.hpp"', + f'#include "{include_prefix_path}/pattern.hpp"', + f'#include "{include_prefix_path}/types.hpp"', + ] + + std_includes = [""] + if intermediate_uses.sets_in(symbol_table.verification_functions): + std_includes.append("") + + if len(public_generic_verifications) > 0: + our_includes.extend( + [ + f'#include "{include_prefix_path}/constants.hpp"', + f'#include "{include_prefix_path}/stringification.hpp"', + ] + ) + + std_includes.extend(["", "", ""]) + + # NOTE (mristin): + # See the includes in :py:func:`generate_implementation`. + if intermediate_uses.abs_call(symbol_table): + std_includes.extend(["", ""]) + + if intermediate_uses.assert_statements_in(public_generic_verifications): + std_includes.append("") + + our_includes_joined = "\n".join(sorted(our_includes)) + std_includes_joined = "\n".join( + f"#include {std_include}" for std_include in sorted(std_includes) + ) + blocks = [ Stripped( f"""\ @@ -215,14 +620,10 @@ def generate_header( cpp_common.WARNING, Stripped( f"""\ -#include "{include_prefix_path}/common.hpp" -#include "{include_prefix_path}/iteration.hpp" -#include "{include_prefix_path}/pattern.hpp" -#include "{include_prefix_path}/types.hpp" +{our_includes_joined} #pragma warning(push, 0) -#include -{unordered_set_include}\ +{std_includes_joined} #pragma warning(pop)""" ), cpp_common.generate_namespace_opening(library_namespace), @@ -466,6 +867,46 @@ class RecursiveVerification : public IVerification {{ blocks.append(Stripped("// endregion Verification functions")) + # NOTE (mristin): + # The templates need to be defined in the header so that the users of + # the library can instantiate them. We define them after all the declarations of + # the verification functions, since the templates look up the functions which + # they call at their definitions. + if len(public_generic_verifications) > 0: + blocks.append( + Stripped("// region Definitions of generic verification functions") + ) + + base_environment = intermediate_type_inference.populate_base_environment( + symbol_table=symbol_table + ) + + for verification in public_generic_verifications: + assert isinstance(verification, intermediate.TranspilableVerification) + + internal_call_errors = _check_no_calls_to_internal_verifications( + verification=verification, symbol_table=symbol_table + ) + if len(internal_call_errors) > 0: + errors.extend(internal_call_errors) + continue + + block, error = _generate_implementation_of_transpilable_verification( + verification=verification, + symbol_table=symbol_table, + base_environment=base_environment, + ) + if error is not None: + errors.append(error) + continue + + assert block is not None + blocks.append(block) + + blocks.append( + Stripped("// endregion Definitions of generic verification functions") + ) + if len(symbol_table.constrained_primitives) > 0: blocks.append(Stripped("// region Verification of constrained primitives")) @@ -667,202 +1108,6 @@ def _generate_pattern_verification_implementation( ) -class _TranspilableVerificationTranspiler(cpp_transpilation.Transpiler): - """Transpile the body of a :class:`TranspilableVerification`.""" - - # fmt: off - @require( - lambda environment, verification: - all( - environment.find(arg.name) is not None - for arg in verification.arguments - ), - "All arguments defined in the environment" - ) - # fmt: on - def __init__( - self, - type_map: Mapping[ - parse_tree.Node, intermediate_type_inference.TypeAnnotationUnion - ], - is_optional_map: Mapping[parse_tree.Node, bool], - downcast_map: Mapping[parse_tree.Node, intermediate_type_inference.Downcast], - is_optional_before_downcast_map: Mapping[parse_tree.Node, bool], - environment: intermediate_type_inference.Environment, - symbol_table: intermediate.SymbolTable, - verification: intermediate.TranspilableVerification, - aliasing: cpp_aliasing.Aliasing, - ) -> None: - """Initialize with the given values.""" - cpp_transpilation.Transpiler.__init__( - self, - type_map=type_map, - is_optional_map=is_optional_map, - downcast_map=downcast_map, - is_optional_before_downcast_map=is_optional_before_downcast_map, - environment=environment, - types_namespace=cpp_common.TYPES_NAMESPACE, - aliasing=aliasing, - ) - - self._symbol_table = symbol_table - - self._argument_name_set = frozenset(arg.name for arg in verification.arguments) - - def transform_name( - self, node: parse_tree.Name - ) -> Tuple[Optional[Stripped], Optional[Error]]: - if node.identifier in self._variable_name_set: - return Stripped(cpp_naming.variable_name(node.identifier)), None - - if node.identifier in self._argument_name_set: - return Stripped(cpp_naming.argument_name(node.identifier)), None - - if node.identifier in self._symbol_table.constants_by_name: - constant = cpp_naming.constant_name(node.identifier) - return Stripped(f"{cpp_common.CONSTANTS_NAMESPACE}::{constant}"), None - - if node.identifier in self._symbol_table.verification_functions_by_name: - return Stripped(cpp_naming.function_name(node.identifier)), None - - our_type = self._symbol_table.find_our_type(name=node.identifier) - if isinstance(our_type, intermediate.Enumeration): - return ( - Stripped( - f"{cpp_common.TYPES_NAMESPACE}::{cpp_naming.enum_name(node.identifier)}" - ), - None, - ) - - return None, Error( - node.original_node, - f"We can not determine how to transpile the name {node.identifier!r} " - f"to C++. We could not find it neither in the constants, nor in " - f"verification functions, nor as an enumeration. " - f"If you expect this name to be transpilable, please contact " - f"the developers.", - ) - - -@ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) -def _generate_implementation_of_transpilable_verification( - verification: intermediate.TranspilableVerification, - symbol_table: intermediate.SymbolTable, - base_environment: intermediate_type_inference.Environment, -) -> Tuple[Optional[Stripped], Optional[Error]]: - """Transpile the verification to a function implementation.""" - # fmt: off - type_inference, inference_error = ( - intermediate_type_inference.infer_for_verification( - verification=verification, - base_environment=base_environment - ) - ) - # fmt: on - - if inference_error is not None: - return None, inference_error - - assert type_inference is not None - - aliasing, aliasing_errors = cpp_aliasing.analyze( - body=verification.parsed.body, - arguments=verification.arguments, - type_map=type_inference.type_map, - binds_self=False, - ) - if aliasing_errors is not None: - return None, Error( - verification.parsed.node, - f"Failed to transpile the verification function {verification.name!r} " - f"so that its C++ variables alias the values as in Python", - aliasing_errors, - ) - - assert aliasing is not None - - optional_inferrer = cpp_optionaling.Inferrer( - environment=type_inference.environment_with_args, - type_map=type_inference.type_map, - downcast_map=type_inference.downcast_map, - ) - for node in verification.parsed.body: - _ = optional_inferrer.transform(node) - - if len(optional_inferrer.errors) > 0: - return None, Error( - verification.parsed.node, - f"Failed to infer whether one or more nodes are ``common::optional`` " - f"in the verification function {verification.name!r}", - optional_inferrer.errors, - ) - - transpiler = _TranspilableVerificationTranspiler( - type_map=type_inference.type_map, - is_optional_map=optional_inferrer.is_optional_map, - downcast_map=type_inference.downcast_map, - is_optional_before_downcast_map=( - optional_inferrer.is_optional_before_downcast_map - ), - environment=type_inference.environment_with_args, - symbol_table=symbol_table, - verification=verification, - aliasing=aliasing, - ) - - body = [] # type: List[Stripped] - for node in verification.parsed.body: - stmt, error = transpiler.transform(node) - if error is not None: - return None, Error( - verification.parsed.node, - f"Failed to transpile the verification function {verification.name!r}", - [error], - ) - - assert stmt is not None - body.append(stmt) - - arg_types_names = [ - ( - cpp_common.generate_argument_type( - argument=arg, types_namespace=cpp_common.TYPES_NAMESPACE - ), - cpp_naming.argument_name(arg.name), - ) - for arg in verification.arguments - ] - - function_name = cpp_naming.function_name(verification.name) - arg_definitions_joined = ",\n".join( - f"{arg_type} {arg_name}" for arg_type, arg_name in arg_types_names - ) - - body_joined = "\n".join(body) - - return_type = _generate_return_type(verification) - - if len(arg_types_names) == 0: - signature = Stripped(f"{return_type} {function_name}()") - else: - signature = Stripped( - f"""\ -{return_type} {function_name}( -{I}{indent_but_first_line(arg_definitions_joined, I)} -)""" - ) - - return ( - Stripped( - f"""\ -{signature} {{ -{I}{indent_but_first_line(body_joined, I)} -}}""" - ), - None, - ) - - class _InvariantTranspiler(cpp_transpilation.Transpiler): """ Transpile invariants of the classes and of the constrained primitives. @@ -3439,6 +3684,15 @@ def generate_implementation( ) elif isinstance(verification, intermediate.TranspilableVerification): + # NOTE (mristin): + # The public generic verification functions are templates defined + # in the header, see :py:func:`generate_header`. + if ( + _is_generic(verification) + and verification.visibility is intermediate.Visibility.PUBLIC + ): + continue + block, error = _generate_implementation_of_transpilable_verification( verification=verification, symbol_table=symbol_table, diff --git a/aas_core_codegen/cpp/transpilation.py b/aas_core_codegen/cpp/transpilation.py index f6d12662b..47702cec7 100644 --- a/aas_core_codegen/cpp/transpilation.py +++ b/aas_core_codegen/cpp/transpilation.py @@ -465,6 +465,27 @@ def __init__( # While this class does not directly use it, the descendants of this class do! self._variable_name_set = set() # type: Set[Identifier] + def _type_parameter_of( + self, type_annotation: intermediate_type_inference.TypeAnnotationUnion + ) -> Optional[Identifier]: + """ + Determine the template parameter which stands for the ``type_annotation``. + + Only the verification functions are templates, namely over the items of + the generic sequences, see :py:func:`intermediate.generic_sequence_item_class`. + The transpiler of the verification functions overrides this method. + + We declare the loop and the generator variables over a generic sequence with + the template parameter so that the items are not converted to + temporary shared pointers of the parent class. + + :param type_annotation: inferred type of a value + :return: + the template parameter, if the value is an item of a generic sequence, + and None otherwise + """ + return None + def _transform_enumeration_literal( self, enumeration_name: Identifier, literal_name: Identifier ) -> Stripped: @@ -1207,6 +1228,52 @@ def transform_is_in( if isinstance(container_type, intermediate_type_inference.ListTypeAnnotation): member = self._as_int64_position(node.member, member) + # NOTE (mristin): + # The ``std::find`` compares the shared pointers by their raw pointers, so + # the classes of the items and of the member need to be related. This holds + # for the declared classes, as the type inference checks that one is + # a subclass of the other. However, the template parameter of a generic + # sequence, see :py:func:`intermediate.generic_sequence_item_class`, can be + # any descendant, *e.g.*, a sibling of the class of the member. Hence, we + # convert the member to the more general of the two declared classes. + member_type = self.type_map[node.member] + if ( + isinstance(container_type, intermediate_type_inference.ListTypeAnnotation) + and isinstance( + container_type.items, intermediate_type_inference.OurTypeAnnotation + ) + and isinstance(container_type.items.our_type, intermediate.Class) + and isinstance(member_type, intermediate_type_inference.OurTypeAnnotation) + and isinstance(member_type.our_type, intermediate.Class) + and ( + self._type_parameter_of(container_type.items) is not None + or self._type_parameter_of(member_type) is not None + ) + ): + general_type = ( + container_type.items + if member_type.our_type.is_subclass_of(container_type.items.our_type) + else member_type + ) + + general_type_cpp, error_msg = generate_type( + type_annotation=intermediate_type_inference.OurTypeAnnotation( + our_type=general_type.our_type + ), + types_namespace=self._types_namespace, + ) + if error_msg is not None: + return None, Error(node.member.original_node, error_msg) + + assert general_type_cpp is not None + + member = Stripped( + f"""\ +{general_type_cpp}( +{I}{indent_but_first_line(member, I)} +)""" + ) + contains_function = cpp_naming.function_name(Identifier("contains")) return ( @@ -1694,6 +1761,49 @@ def transform_function_call( assert function_name is not None + # NOTE (mristin): + # A transpiled verification function is a template over the items of + # its generic sequences, see + # :py:func:`intermediate.generic_sequence_item_class`. C++ deduces + # the template parameters without any implicit conversions, so we pass + # ``None`` to an optional generic sequence as a typed empty optional, + # and a required vector through ``common::make_optional``. + if isinstance(func_type.func, intermediate.TranspilableVerification): + for i, (arg_node, argument) in enumerate( + zip(node.args, func_type.func.arguments) + ): + if intermediate.generic_sequence_item_class(argument) is None: + continue + + if not isinstance( + argument.type_annotation, intermediate.OptionalTypeAnnotation + ): + continue + + if ( + isinstance(arg_node, parse_tree.Constant) + and arg_node.value is None + ): + optional_type = cpp_common.generate_type( + type_annotation=argument.type_annotation, + types_namespace=self._types_namespace, + ) + args[i] = Stripped(f"{optional_type}()") + + elif not isinstance( + self.type_map[arg_node], + intermediate_type_inference.OptionalTypeAnnotation, + ): + args[i] = Stripped( + f"""\ +common::make_optional( +{I}{indent_but_first_line(args[i], I)} +)""" + ) + + else: + pass + if len(args) == 0: return Stripped(f"{function_name}()"), None @@ -2425,8 +2535,18 @@ def _transform_any_or_all( cpp_aliasing.Declaration.MUT_REF ) + type_parameter = self._type_parameter_of(variable_type_annotation) + variable_type_cpp: Optional[Stripped] - if self._declaration(node.generator) is cpp_aliasing.Declaration.COPY: + error_msg: Optional[str] + if type_parameter is not None: + variable_type_cpp = Stripped(f"std::shared_ptr<{type_parameter}>") + error_msg = None + + if self._declaration(node.generator) is not cpp_aliasing.Declaration.COPY: + variable_type_cpp = Stripped(f"const {variable_type_cpp}&") + + elif self._declaration(node.generator) is cpp_aliasing.Declaration.COPY: variable_type_cpp, error_msg = generate_type( type_annotation=variable_type_annotation, types_namespace=self._types_namespace, @@ -2749,6 +2869,27 @@ def transform_assignment( assert declared_type is not None + # NOTE (mristin): + # An immutable alias of a generic sequence shares its type annotation, + # see :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`, + # so we declare the alias with the template parameter as well. + target_type_beneath = intermediate_type_inference.beneath_optional( + target_type + ) + if isinstance( + target_type_beneath, intermediate_type_inference.ListTypeAnnotation + ): + type_parameter = self._type_parameter_of(target_type_beneath.items) + if type_parameter is not None: + declared_type = Stripped( + f"std::vector >" + ) + + if isinstance( + target_type, intermediate_type_inference.OptionalTypeAnnotation + ): + declared_type = Stripped(f"common::optional<{declared_type} >") + # NOTE (mristin): # A variable declared as ``Final[...]`` is neither re-assigned nor # mutated, so we can declare it as ``const``. @@ -3104,10 +3245,18 @@ def transform_for( header: Optional[str] = None if isinstance(node.generator, parse_tree.ForEach): - variable_type_cpp, error_msg = generate_type( - type_annotation=variable_type_annotation, - types_namespace=self._types_namespace, - ) + type_parameter = self._type_parameter_of(variable_type_annotation) + + variable_type_cpp: Optional[Stripped] + error_msg: Optional[str] + if type_parameter is not None: + variable_type_cpp = Stripped(f"std::shared_ptr<{type_parameter}>") + error_msg = None + else: + variable_type_cpp, error_msg = generate_type( + type_annotation=variable_type_annotation, + types_namespace=self._types_namespace, + ) if error_msg is not None: errors.append(Error(node.generator.variable.original_node, error_msg)) diff --git a/aas_core_codegen/csharp/lib/_generate_verification.py b/aas_core_codegen/csharp/lib/_generate_verification.py index 658bb0d31..514b83c21 100644 --- a/aas_core_codegen/csharp/lib/_generate_verification.py +++ b/aas_core_codegen/csharp/lib/_generate_verification.py @@ -472,10 +472,41 @@ def _transpile_transpilable_verification( assert stmt is not None body.append(stmt) + # NOTE (mristin): + # The ``List`` is invariant in C#, so we declare the generic sequences, + # see :py:func:`intermediate.generic_sequence_item_class`, as the covariant + # ``IReadOnlyList``. This way, the lists of the descendants can be passed + # without a copy, as ``List`` implements ``IReadOnlyList``. + arg_defs = [] # type: List[Stripped] + remarks_on_generic_sequences = [] # type: List[str] + for arg in verification.arguments: + arg_name = csharp_naming.argument_name(arg.name) + + if intermediate.generic_sequence_item_class(arg) is None: + arg_type = csharp_common.generate_type(arg.type_annotation) + else: + list_type_anno = intermediate.beneath_optional(arg.type_annotation) + assert isinstance(list_type_anno, intermediate.ListTypeAnnotation) + + item_type = csharp_common.generate_type(list_type_anno.items) + + arg_type = Stripped(f"IReadOnlyList<{item_type}>") + if isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation): + arg_type = Stripped(f"{arg_type}?") + + remarks_on_generic_sequences.append( + f"The argument {arg_name} is declared as the covariant " + f"IReadOnlyList<{item_type}> instead of the invariant " + f"List<{item_type}> so that the lists of the descendants of " + f"{item_type} can be passed without a copy." + ) + + arg_defs.append(Stripped(f"{arg_type} {arg_name}")) + writer = io.StringIO() if verification.description is not None: comment, comment_errors = csharp_description.generate_comment_for_signature( - verification.description + verification.description, extra_remarks=remarks_on_generic_sequences ) if comment_errors is not None: return None, Error( @@ -488,6 +519,15 @@ def _transpile_transpilable_verification( writer.write(comment) writer.write("\n") + elif len(remarks_on_generic_sequences) > 0: + writer.write( + csharp_description.generate_comment_for_remarks( + remarks_on_generic_sequences + ) + ) + writer.write("\n") + else: + pass method_name = csharp_naming.method_name(verification.name) @@ -502,12 +542,6 @@ def _transpile_transpilable_verification( else: return_type = csharp_common.generate_type(type_annotation=verification.returns) - arg_defs = [] # type: List[Stripped] - for arg in verification.arguments: - arg_type = csharp_common.generate_type(arg.type_annotation) - arg_name = csharp_naming.argument_name(arg.name) - arg_defs.append(Stripped(f"{arg_type} {arg_name}")) - if len(arg_defs) == 0: writer.write( f"""\ diff --git a/aas_core_codegen/csharp/transpilation.py b/aas_core_codegen/csharp/transpilation.py index 14ae6cca6..244792867 100644 --- a/aas_core_codegen/csharp/transpilation.py +++ b/aas_core_codegen/csharp/transpilation.py @@ -1860,6 +1860,19 @@ def transform_assignment( target, error = self.transform_name(node=node.target) if error is not None: errors.append(error) + elif ( + node.annotation is not None + and isinstance(node.value, parse_tree.Name) + and self.type_map[node.target] is self.type_map[node.value] + ): + # NOTE (mristin): + # An immutable alias shares the type annotation of the aliased + # variable, see + # :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`, + # so we infer its type with ``var``. This way, the alias of a generic + # sequence is also declared as ``IReadOnlyList``, see + # :py:func:`intermediate.generic_sequence_item_class`. + target = Stripped(f"var {target}") elif node.annotation is not None: # NOTE (mristin): # We spell out the declared type, as it might differ from the type diff --git a/aas_core_codegen/golang/lib/_generate_common.py b/aas_core_codegen/golang/lib/_generate_common.py index 98f7b0837..7d1e002ab 100644 --- a/aas_core_codegen/golang/lib/_generate_common.py +++ b/aas_core_codegen/golang/lib/_generate_common.py @@ -382,6 +382,24 @@ def generate(symbol_table: intermediate.SymbolTable) -> str: {II}}} {I}}} {I}return false +}}""" + ), + Stripped( + f"""\ +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool {{ +{I}for _, item := range s {{ +{II}if any(item) == v {{ +{III}return true +{II}}} +{I}}} +{I}return false }}""" ), Stripped( diff --git a/aas_core_codegen/golang/lib/_generate_verification.py b/aas_core_codegen/golang/lib/_generate_verification.py index f78d9d4d1..c5874dd95 100644 --- a/aas_core_codegen/golang/lib/_generate_verification.py +++ b/aas_core_codegen/golang/lib/_generate_verification.py @@ -11,6 +11,7 @@ Mapping, Union, Set, + Dict, ) from icontract import ensure, require @@ -411,6 +412,15 @@ def _transpile_pattern_verification( return Stripped(writer.getvalue()), None +def _type_parameter_name(argument: intermediate.Argument) -> Identifier: + """ + Generate the name of the type parameter for the items of the ``argument``. + + For example, ``items`` gives ``ItemsT``. + """ + return Identifier(f"{golang_naming.function_name(argument.name)}T") + + class _TranspilableVerificationTranspiler(golang_transpilation.Transpiler): """Transpile the body of a :py:class:`TranspilableVerification`.""" @@ -449,6 +459,35 @@ def __init__( self._argument_name_set = frozenset(arg.name for arg in verification.arguments) + # NOTE (mristin): + # The type inference propagates the very type annotation of the items of + # an argument to the loop variables, the generator variables and + # the indexed items, so we can tell by the identity of the type annotation + # that a value is of the type parameter of a generic sequence. + type_parameter_by_items_id = dict() # type: Dict[int, Identifier] + for arg in verification.arguments: + if intermediate.generic_sequence_item_class(arg) is None: + continue + + arg_type_anno = environment.find(arg.name) + assert arg_type_anno is not None + + list_type_anno = intermediate_type_inference.beneath_optional(arg_type_anno) + assert isinstance( + list_type_anno, intermediate_type_inference.ListTypeAnnotation + ) + + type_parameter_by_items_id[id(list_type_anno.items)] = _type_parameter_name( + arg + ) + + self._type_parameter_by_items_id = type_parameter_by_items_id + + def _type_parameter_of( + self, type_annotation: intermediate_type_inference.TypeAnnotationUnion + ) -> Optional[Identifier]: + return self._type_parameter_by_items_id.get(id(type_annotation), None) + def _transform_enumeration_literal( self, enumeration_name: Identifier, literal_name: Identifier ) -> Stripped: @@ -548,6 +587,46 @@ def _transpile_transpilable_verification( assert stmt is not None body.append(stmt) + # NOTE (mristin): + # The slices are invariant in Go, so we declare a type parameter for the items + # of each generic sequence, see :py:func:`intermediate.generic_sequence_item_class`. + # This way, the slices of the descendants can be passed without a copy. + # The methods can not have type parameters in Go, so only the verification + # functions have generic sequences. + arg_defs = [] # type: List[Stripped] + type_parameter_defs = [] # type: List[Stripped] + remarks_on_generic_sequences = [] # type: List[str] + for arg in verification.arguments: + arg_name = golang_naming.argument_name(arg.name) + + if intermediate.generic_sequence_item_class(arg) is None: + arg_type = golang_common.generate_type( + arg.type_annotation, types_package=Identifier("ourtypes") + ) + else: + list_type_anno = intermediate.beneath_optional(arg.type_annotation) + assert isinstance(list_type_anno, intermediate.ListTypeAnnotation) + + item_type = golang_common.generate_type( + list_type_anno.items, types_package=Identifier("ourtypes") + ) + + type_parameter = _type_parameter_name(arg) + type_parameter_defs.append(Stripped(f"{type_parameter} {item_type}")) + + # NOTE (mristin): + # The optional slices are not pointers in Go, as the slices are nilable. + arg_type = Stripped(f"[]{type_parameter}") + + remarks_on_generic_sequences.append( + f"The items of the argument {arg_name} are of the type parameter " + f"{type_parameter} instead of {item_type} so that the slices of " + f"the descendants of {item_type} can be passed without a copy, as " + f"the slices are invariant in Go." + ) + + arg_defs.append(Stripped(f"{arg_name} {arg_type}")) + writer = io.StringIO() if verification.description is not None: @@ -557,6 +636,7 @@ def _transpile_transpilable_verification( package=golang_common.VERIFICATION_PACKAGE, cls_or_enum=None, ), + extra_remarks=remarks_on_generic_sequences, ) if comment_errors is not None: return None, Error( @@ -570,9 +650,22 @@ def _transpile_transpilable_verification( writer.write(comment) writer.write("\n") + elif len(remarks_on_generic_sequences) > 0: + writer.write( + golang_description.documentation_comment( + Stripped("\n\n".join(remarks_on_generic_sequences)) + ) + ) + writer.write("\n") + else: + pass function_name = golang_naming.function_name(verification.name) + type_parameters = ( + f"[{', '.join(type_parameter_defs)}]" if len(type_parameter_defs) > 0 else "" + ) + if verification.returns is None: return_type_suffix = "" else: @@ -581,24 +674,16 @@ def _transpile_transpilable_verification( ) return_type_suffix = f" {return_type}" - arg_defs = [] # type: List[Stripped] - for arg in verification.arguments: - arg_type = golang_common.generate_type( - arg.type_annotation, types_package=Identifier("ourtypes") - ) - arg_name = golang_naming.argument_name(arg.name) - arg_defs.append(Stripped(f"{arg_name} {arg_type}")) - if len(arg_defs) == 0: writer.write( f"""\ -func {function_name}(){return_type_suffix} {{""" +func {function_name}{type_parameters}(){return_type_suffix} {{""" ) else: arg_defs_joined = "\n".join(f"{arg_def}," for arg_def in arg_defs) writer.write( f"""\ -func {function_name}( +func {function_name}{type_parameters}( {I}{indent_but_first_line(arg_defs_joined, I)} ){return_type_suffix} {{""" ) diff --git a/aas_core_codegen/golang/transpilation.py b/aas_core_codegen/golang/transpilation.py index de362bd3f..42f9e6de2 100644 --- a/aas_core_codegen/golang/transpilation.py +++ b/aas_core_codegen/golang/transpilation.py @@ -256,6 +256,24 @@ def __init__( # generate the reference in the Go code. self._variable_name_set = set() # type: Set[Identifier] + def _type_parameter_of( + self, type_annotation: intermediate_type_inference.TypeAnnotationUnion + ) -> Optional[Identifier]: + """ + Determine the type parameter which stands for the ``type_annotation``. + + Only the verification functions have type parameters, namely for the items + of the generic sequences, see + :py:func:`intermediate.generic_sequence_item_class`. The transpiler of + the verification functions overrides this method. + + :param type_annotation: inferred type of a value + :return: + the type parameter, if the value is an item of a generic sequence, + and None otherwise + """ + return None + def _our_type_name(self, name: Identifier) -> Stripped: """Prepend the types package, if specified, to the ``name`` of our type.""" if self._types_package is None: @@ -309,6 +327,12 @@ def transform( source_type = downcast.source.our_type if isinstance(source_type, intermediate.Class): + # NOTE (mristin): + # Go forbids type assertions on the values of a type parameter, so we + # convert the item of a generic sequence to an interface first. + if self._type_parameter_of(downcast.source) is not None: + return Stripped(f"any({code}).({interface_name})"), None + return Stripped(f"{code}.({interface_name})"), None elif isinstance(source_type, intermediate.NamedUnion): @@ -808,10 +832,24 @@ def transform_is_in( ) elif isinstance(container_type, intermediate_type_inference.ListTypeAnnotation): + # NOTE (mristin): + # The ``ourcommon.SliceContains`` infers its type parameter from both + # the slice and the member, so that the untyped constants take the type of + # the items. The instances of the classes are never constants, but they + # can be of a different type than the items, *e.g.*, if the items are of + # the type parameter of a generic sequence, see + # :py:func:`intermediate.generic_sequence_item_class`, or if the member + # is an instance of a descendant. Hence, we look them up as ``any``'s. + function_name = "SliceContains" + if isinstance( + container_type.items, intermediate_type_inference.OurTypeAnnotation + ) and isinstance(container_type.items.our_type, intermediate.Class): + function_name = "SliceContainsInstance" + return ( Stripped( f"""\ -ourcommon.SliceContains( +ourcommon.{function_name}( {I}{indent_but_first_line(container, I)}, {I}{indent_but_first_line(member, I)}, )""" @@ -1146,6 +1184,32 @@ def transform_function_call( args = [] # type: List[Stripped] for i, arg_node in enumerate(node.args): + # NOTE (mristin): + # The type parameter of a generic sequence can not be inferred from + # an untyped ``nil``, so we pass on a typed ``nil`` instead, see + # :py:func:`intermediate.generic_sequence_item_class`. + if ( + isinstance( + func_type, intermediate_type_inference.VerificationTypeAnnotation + ) + and isinstance(arg_node, parse_tree.Constant) + and arg_node.value is None + and intermediate.generic_sequence_item_class( + func_type.func.arguments[i] + ) + is not None + ): + list_type_anno = intermediate.beneath_optional( + func_type.func.arguments[i].type_annotation + ) + assert isinstance(list_type_anno, intermediate.ListTypeAnnotation) + + list_type = golang_common.generate_type( + type_annotation=list_type_anno, types_package=self._types_package + ) + args.append(Stripped(f"{list_type}(nil)")) + continue + # NOTE (mristin): # We pass on a pointer to an optional argument of a verification # function, and de-reference the values otherwise. @@ -2066,11 +2130,22 @@ def _transform_any_or_all( variable_type_annotation = self.type_map[node.generator.variable] variable_name_go = golang_naming.variable_name(variable_name) - variable_type_go, error_msg = generate_type( - type_annotation=variable_type_annotation, types_package=self._types_package - ) - if error_msg is not None: - errors.append(Error(node.generator.variable.original_node, error_msg)) + + # NOTE (mristin): + # The items of a generic sequence are of the type parameter, and + # the ``ourcommon.All`` and ``ourcommon.Some`` infer their type from both + # the condition and the slice, so the condition needs to expect the type + # parameter as well. + type_parameter = self._type_parameter_of(variable_type_annotation) + if type_parameter is not None: + variable_type_go = Stripped(type_parameter) # type: Optional[Stripped] + else: + variable_type_go, error_msg = generate_type( + type_annotation=variable_type_annotation, + types_package=self._types_package, + ) + if error_msg is not None: + errors.append(Error(node.generator.variable.original_node, error_msg)) try: self._environment.set( @@ -2410,6 +2485,21 @@ def transform_assignment( if error_msg is not None: return None, Error(node.annotation.original_node, error_msg) + # NOTE (mristin): + # An immutable alias of a generic sequence shares its type annotation, + # see :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`, + # so we declare the alias with the type parameter as well. The optional + # slices are not pointers, as the slices are nilable. + target_type_beneath = intermediate_type_inference.beneath_optional( + self.type_map[node.target] + ) + if isinstance( + target_type_beneath, intermediate_type_inference.ListTypeAnnotation + ): + type_parameter = self._type_parameter_of(target_type_beneath.items) + if type_parameter is not None: + declared_type = Stripped(f"[]{type_parameter}") + target = Stripped(f"var {target} {declared_type}") assignment = "=" diff --git a/aas_core_codegen/intermediate/__init__.py b/aas_core_codegen/intermediate/__init__.py index 3304b2561..ae47e1e8e 100644 --- a/aas_core_codegen/intermediate/__init__.py +++ b/aas_core_codegen/intermediate/__init__.py @@ -77,6 +77,7 @@ try_primitive_type = _types.try_primitive_type try_constrained_primitive = _types.try_constrained_primitive map_descendability = _types.map_descendability +generic_sequence_item_class = _types.generic_sequence_item_class collect_ids_of_our_types_in_properties = _types.collect_ids_of_our_types_in_properties over_type_annotation_and_nested_type_annotations = ( _types.over_type_annotation_and_nested_type_annotations diff --git a/aas_core_codegen/intermediate/_types.py b/aas_core_codegen/intermediate/_types.py index 82df4ac91..8a167e510 100644 --- a/aas_core_codegen/intermediate/_types.py +++ b/aas_core_codegen/intermediate/_types.py @@ -3855,6 +3855,51 @@ def try_constrained_primitive( return None +def generic_sequence_item_class(argument: Argument) -> Optional["ClassUnion"]: + """ + Determine the class of the items if the ``argument`` is a generic sequence. + + A generic sequence is a read-only ``Sequence[...]`` of instances of a class + with descendants, possibly beneath ``Optional[...]``. The verification functions + accept the sequences of the descendants for such an argument, as the type + inference allows it in :py:mod:`aas_core_codegen.intermediate.type_inference`. + + Python and TypeScript accept the sequences of the descendants for free, as their + read-only sequences are covariant. The lists of the other targets are invariant, + so they declare the argument generic: Go with a type parameter, C++ with + a template defined in the header, C# with the covariant ``IReadOnlyList``, and + Java with the wildcard ``List``. None of them copies the sequence. + + The methods can not have generic arguments, since Go methods can not have type + parameters, and C++ virtual methods can not be templates. Hence, the generators + should check this only for the verification functions. + + A mutable ``List[...]`` is never generic, since the callee could insert into it + an instance which is not of the caller's subclass. + + :param argument: of a verification function + :return: the class of the items, if the argument is a generic sequence + """ + if argument.mutable: + return None + + type_anno = beneath_optional(argument.type_annotation) + if not isinstance(type_anno, ListTypeAnnotation): + return None + + if not isinstance(type_anno.items, OurTypeAnnotation): + return None + + our_type = type_anno.items.our_type + if not isinstance(our_type, (AbstractClass, ConcreteClass)): + return None + + if len(our_type.descendant_id_set) == 0: + return None + + return our_type + + def map_descendability( type_annotation: TypeAnnotationUnion, ) -> MutableMapping[TypeAnnotationUnion, bool]: diff --git a/aas_core_codegen/intermediate/type_inference.py b/aas_core_codegen/intermediate/type_inference.py index 47b161096..2950b9119 100644 --- a/aas_core_codegen/intermediate/type_inference.py +++ b/aas_core_codegen/intermediate/type_inference.py @@ -976,6 +976,46 @@ def _assignable( return False +def _is_list_of_descendants( + target_type: "TypeAnnotationUnion", value_type: "TypeAnnotationUnion" +) -> bool: + """ + Check that the ``value_type`` is a list of the descendants of the items of + the ``target_type``. + + An optional value can be passed only to an optional target. + """ + if isinstance(value_type, OptionalTypeAnnotation) and not isinstance( + target_type, OptionalTypeAnnotation + ): + return False + + target_type_beneath = beneath_optional(target_type) + value_type_beneath = beneath_optional(value_type) + + if not ( + isinstance(target_type_beneath, ListTypeAnnotation) + and isinstance(value_type_beneath, ListTypeAnnotation) + ): + return False + + target_items = target_type_beneath.items + value_items = value_type_beneath.items + + if not ( + isinstance(target_items, OurTypeAnnotation) + and isinstance(target_items.our_type, _types.Class) + and isinstance(value_items, OurTypeAnnotation) + and isinstance(value_items.our_type, _types.Class) + ): + return False + + return ( + _types.runtime_id(value_items.our_type) + in target_items.our_type.descendant_id_set + ) + + def needs_wrapping_into_named_union( target_type: "TypeAnnotationUnion", value_type: "TypeAnnotationUnion" ) -> bool: @@ -2289,22 +2329,6 @@ def _can_be_mutated(type_annotation: "TypeAnnotationUnion") -> bool: ) # type: Final -def _holds_set(type_annotation: "TypeAnnotationUnion") -> bool: - """Check whether a value of ``type_annotation`` is or contains a set at any depth.""" - type_anno = beneath_optional(type_annotation) - - if isinstance(type_anno, SetTypeAnnotation): - return True - - if isinstance(type_anno, ListTypeAnnotation): - return _holds_set(type_anno.items) - - if isinstance(type_anno, TupleTypeAnnotation): - return any(_holds_set(item) for item in type_anno.items) - - return False - - def _holds_list_or_set(type_annotation: "TypeAnnotationUnion") -> bool: """Check whether a value of ``type_annotation`` is or contains a list or a set.""" type_anno = beneath_optional(type_annotation) @@ -4334,41 +4358,78 @@ def _transform_builtin_method_call( self.type_map[node] = result return result - def _check_arguments( + def _check_call_arguments( self, args: Sequence[parse_tree.Expression], - arguments: Sequence[_types.Argument], - what: str, + callee: Union[_types.Verification, _types.Method], ) -> bool: """ - Check that the ``args`` fit the ``arguments`` beyond their types. + Check that the ``args`` fit the arguments of the ``callee``. - We check that the ``args`` passed to the mutable ``arguments`` are mutable, - and that ``None`` is passed only to the optional ``arguments``. + The lists and the sets are invariant in their items, see + :py:func:`_assignable`, as the targets represent them with invariant types. + The only exception is a read-only ``Sequence[...]`` argument of + a verification function, which also accepts a list of the descendants of + its items, as the targets declare it generic, see + :py:func:`aas_core_codegen.intermediate.generic_sequence_item_class`. + The methods can not have generic arguments, since Go methods can not have + type parameters, and C++ virtual methods can not be templates. - The ``what`` describes the called function or method in the error messages, - *e.g.*, ``the verification function 'foo'``. + We further check that ``None`` is passed only to the optional arguments, and + that the ``args`` passed to the mutable arguments are mutable. :return: True if the check passed """ + what = ( + f"the verification function {callee.name!r}" + if isinstance(callee, _types.Verification) + else f"the method {callee.name!r}" + ) + ok = True - for arg_node, argument in zip(args, arguments): + for arg_node, argument in zip(args, callee.arguments): # NOTE (mristin): # We do not check the types of the arguments in general. However, we - # have to check the sets, since the targets represent them with - # different types which only the checks here make compatible. + # have to check the lists and the sets, see the docstring. arg_type = self.type_map.get(arg_node, None) argument_type = convert_type_annotation(argument.type_annotation) + + accepts_descendants = ( + isinstance(callee, _types.Verification) + and not argument.mutable + and arg_type is not None + and _is_list_of_descendants( + target_type=argument_type, value_type=arg_type + ) + ) + if ( arg_type is not None - and (_holds_set(arg_type) or _holds_set(argument_type)) + and (_holds_list_or_set(arg_type) or _holds_list_or_set(argument_type)) and not _assignable(target_type=argument_type, value_type=arg_type) + and not accepts_descendants ): self.errors.append( Error( arg_node.original_node, f"The argument {argument.name!r} of {what} is " - f"of type {argument_type}, but got: {arg_type}", + f"of type {argument_type}, but got: {arg_type}. " + f"The lists and the sets are invariant, so their items " + f"need to be of exactly the same type. A mutable " + f"List[...] can not hold the instances of a subclass, " + f"since the callee could insert an instance of another " + f"subclass into it. Only a read-only Sequence[...] " + f"argument of a verification function accepts " + f"the instances of a subclass, since the targets with " + f"invariant lists declare it generic: Go with a type " + f"parameter, C++ with a template, C# with IReadOnlyList " + f"and Java with List. The methods can not " + f"accept them, since Go methods can not have type " + f"parameters, and C++ virtual methods can not be " + f"templates. We do not copy the list implicitly either, as " + f"that would cost linear time and memory on each call. " + f"Please declare the argument with the exact type of " + f"the items.", ) ) ok = False @@ -4471,11 +4532,7 @@ def transform_method_call( ) return None - if not self._check_arguments( - args=node.args, - arguments=member_type.method.arguments, - what=f"the method {node.member.name!r}", - ): + if not self._check_call_arguments(args=node.args, callee=member_type.method): return None if not member_type.method.non_mutating: @@ -4634,11 +4691,7 @@ def transform_function_call( if isinstance( func_type, VerificationTypeAnnotation - ) and not self._check_arguments( - args=node.args, - arguments=func_type.func.arguments, - what=f"the verification function {func_type.func.name!r}", - ): + ) and not self._check_call_arguments(args=node.args, callee=func_type.func): return None # NOTE (mristin): @@ -6052,6 +6105,20 @@ def transform_assignment( # and the type of the assigned value otherwise. variable_type = target_type if target_type is not None else value_type + # NOTE (mristin): + # An immutable alias of a variable of the very same type shares the type + # annotation of the variable. This way, the transpilers can tell by + # the identity of the type annotation that the alias holds a generic + # sequence, see :py:func:`_types.generic_sequence_item_class`. + if ( + is_new_variable + and is_final + and isinstance(node.value, parse_tree.Name) + and target_type is not None + and _type_annotations_equal(target_type, value_type) + ): + variable_type = value_type + if is_new_variable: assert isinstance(node.target, parse_tree.Name) if node.target.identifier in self._types_of_variables_in_closed_scopes[-1]: @@ -7629,6 +7696,117 @@ def is_appended_list(node: parse_tree.Node) -> bool: return errors +def _check_usages_of_generic_sequences( + body: Sequence[parse_tree.Node], + arguments: Sequence[_types.Argument], + type_map: Mapping[parse_tree.Node, "TypeAnnotationUnion"], +) -> List[Error]: + """ + Check that the generic sequences in the ``body`` are used only where we can + transpile them. + + The ``arguments`` belong to a verification function. A generic sequence, + see :py:func:`aas_core_codegen.intermediate.generic_sequence_item_class`, + accepts the sequences of the descendants. The targets with invariant lists + declare it generic: Go with a type parameter, C++ with a template, C# with + ``IReadOnlyList``, and Java with ``List``. Such an argument + can be only the collection of a for-loop or of ``all``/``any``, the collection + of an index, the container of ``in``, the argument of ``len``, the value of + a nullness check, an argument of a verification function, which is generic + as well, and the value of an immutable alias, *e.g.*, + ``alias: Final[Sequence[Item]] = items``, which is generic as well. Elsewhere, + *e.g.*, passed to a method or assigned to a variable, the targets would expect + the non-generic list, and we would have to copy it. + """ + generic_name_set = { + argument.name + for argument in arguments + if _types.generic_sequence_item_class(argument) is not None + } + + if len(generic_name_set) == 0: + return [] + + allowed_set = set() # type: Set[parse_tree.Node] + generic_names = [] # type: List[parse_tree.Name] + + for node_in_body in body: + for node in parse_tree.over_nodes(node_in_body): + if ( + isinstance(node, parse_tree.Name) + and node.identifier in generic_name_set + ): + generic_names.append(node) + + # NOTE (mristin): + # An immutable alias of a generic sequence is a generic sequence itself, + # since it shares the type annotation of the aliased variable, see + # :py:meth:`_Inferrer.transform_assignment`. The targets declare + # the alias with the type parameter. + if ( + isinstance(node, parse_tree.Assignment) + and node.annotation is not None + and is_final_annotation(node.annotation) + and isinstance(node.target, parse_tree.Name) + and isinstance(node.value, parse_tree.Name) + and node.value.identifier in generic_name_set + and type_map.get(node.target, None) is type_map.get(node.value, None) + ): + allowed_set.add(node.target) + allowed_set.add(node.value) + generic_name_set.add(node.target.identifier) + + if isinstance(node, parse_tree.ForEach): + allowed_set.add(node.iteration) + + elif isinstance(node, parse_tree.Index): + allowed_set.add(node.collection) + + elif isinstance(node, parse_tree.IsIn): + allowed_set.add(node.container) + + elif isinstance(node, (parse_tree.IsNone, parse_tree.IsNotNone)): + allowed_set.add(node.value) + + elif isinstance(node, parse_tree.FunctionCall): + func_type = type_map.get(node.name, None) + + if isinstance(func_type, VerificationTypeAnnotation): + allowed_set.update(node.args) + + elif ( + isinstance(func_type, BuiltinFunctionTypeAnnotation) + and func_type.func.kind is BuiltinFunctionKind.LEN + ): + allowed_set.update(node.args) + + else: + pass + + else: + pass + + return [ + Error( + name.original_node, + f"The argument {name.identifier!r} is a read-only Sequence[...] of " + f"a class with descendants, so the targets declare it generic to " + f"accept the sequences of the descendants without a copy: Go with " + f"a type parameter, C++ with a template, C# with IReadOnlyList, " + f"and Java with List. Hence, we support it only as " + f"the collection of a for-loop or of all/any, the collection of " + f"an index, the container of in, the argument of len, the value of " + f"a nullness check, an argument of a verification function, and " + f"the value of an immutable alias of the very same type, *e.g.*, " + f"``alias: Final[Sequence[...]] = {name.identifier}``. Elsewhere, " + f"*e.g.*, passed to a method, assigned to a mutable variable or copied " + f"with list, the targets would expect the non-generic list.", + ) + for name in generic_names + if name not in allowed_set + ] + + @ensure(lambda result: (result[0] is not None) ^ (result[1] is not None)) def _infer_for_function( body: Sequence[parse_tree.Node], @@ -7678,6 +7856,16 @@ def _infer_for_function( ) ) + # NOTE (mristin): + # Only the verification functions have generic arguments, as the methods can + # not be generic, see :py:func:`_types.generic_sequence_item_class`. + if enclosing_method is None: + type_inferrer.errors.extend( + _check_usages_of_generic_sequences( + body=body, arguments=arguments, type_map=type_inferrer.type_map + ) + ) + # NOTE (mristin): # Some targets, such as Go or Java, refuse to compile a function which misses # a return statement at the end, so we refuse it here already. diff --git a/aas_core_codegen/java/lib/_generate_verification.py b/aas_core_codegen/java/lib/_generate_verification.py index 713d3a058..bd6a540cc 100644 --- a/aas_core_codegen/java/lib/_generate_verification.py +++ b/aas_core_codegen/java/lib/_generate_verification.py @@ -516,11 +516,45 @@ def _transpile_transpilable_verification( assert stmt is not None body.append(stmt) + # NOTE (mristin): + # The ``List`` is invariant in Java, so we declare the generic sequences, + # see :py:func:`intermediate.generic_sequence_item_class`, with the wildcard + # ``List``. This way, the lists of the descendants can be passed + # without a copy. The ``Optional`` is invariant as well, so we declare + # the optional generic sequences as ``Optional>``. + arg_defs = [] # type: List[Stripped] + remarks_on_generic_sequences = [] # type: List[str] + for arg in verification.arguments: + arg_name = java_naming.argument_name(arg.name) + + if intermediate.generic_sequence_item_class(arg) is None: + arg_type = java_common.generate_type(arg.type_annotation) + else: + list_type_anno = intermediate.beneath_optional(arg.type_annotation) + assert isinstance(list_type_anno, intermediate.ListTypeAnnotation) + + item_type = java_common.generate_type(list_type_anno.items) + + arg_type = Stripped(f"List") + if isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation): + arg_type = Stripped(f"Optional") + + remarks_on_generic_sequences.append( + f"The argument {{@code {arg_name}}} is declared with the wildcard " + f"{{@code List}} instead of the invariant " + f"{{@code List<{item_type}>}} so that the lists of " + f"the descendants of {{@link {item_type}}} can be passed without " + f"a copy." + ) + + arg_defs.append(Stripped(f"{arg_type} {arg_name}")) + writer = io.StringIO() if verification.description is not None: comment, comment_errors = java_description.generate_comment_for_signature( description=verification.description, context=java_description.Context(package=package, cls_or_enum=None), + extra_remarks=remarks_on_generic_sequences, ) if comment_errors is not None: return None, Error( @@ -533,6 +567,15 @@ def _transpile_transpilable_verification( writer.write(comment) writer.write("\n") + elif len(remarks_on_generic_sequences) > 0: + writer.write( + java_description.documentation_comment( + Stripped("\n\n".join(remarks_on_generic_sequences)) + ) + ) + writer.write("\n") + else: + pass method_name = java_naming.method_name(verification.name) @@ -550,12 +593,6 @@ def _transpile_transpilable_verification( else: return_type = java_common.generate_type(type_annotation=verification.returns) - arg_defs = [] # type: List[Stripped] - for arg in verification.arguments: - arg_type = java_common.generate_type(arg.type_annotation) - arg_name = java_naming.argument_name(arg.name) - arg_defs.append(Stripped(f"{arg_type} {arg_name}")) - if len(arg_defs) == 0: writer.write( f"""\ diff --git a/aas_core_codegen/java/transpilation.py b/aas_core_codegen/java/transpilation.py index bb284ac6c..d99639196 100644 --- a/aas_core_codegen/java/transpilation.py +++ b/aas_core_codegen/java/transpilation.py @@ -2096,6 +2096,20 @@ def transform_assignment( target, error = self.transform_name(node=node.target) if error is not None: errors.append(error) + elif ( + node.annotation is not None + and isinstance(node.value, parse_tree.Name) + and self.type_map[node.target] is self.type_map[node.value] + ): + # NOTE (mristin): + # An immutable alias shares the type annotation of the aliased + # variable, see + # :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`, + # so we infer its type with ``var``. This way, the alias of + # a generic sequence is also declared with the wildcard + # ``List``, see + # :py:func:`intermediate.generic_sequence_item_class`. + target = Stripped(f"var {target}") elif node.annotation is not None and ( intermediate_type_inference.try_primitive_type(type_anno) is not intermediate_type_inference.PrimitiveType.INT diff --git a/dev/test_data/common_meta_models/variance_of_containers.py b/dev/test_data/common_meta_models/variance_of_containers.py new file mode 100644 index 000000000..99274c58c --- /dev/null +++ b/dev/test_data/common_meta_models/variance_of_containers.py @@ -0,0 +1,407 @@ +""" +Test the variance of the containers passed as arguments. + +A read-only ``Sequence[...]`` argument of a verification function is generic if its +items are instances of a class with descendants, so that the sequences of +the descendants can be passed to it without a copy. The arguments of the methods +and the mutable ``List[...]`` arguments remain invariant. +""" + +from typing import Final, List, Optional, Sequence, Set + +from icontract import DBC, invariant + + +@abstract +@serialization(with_model_type=True) +class Item(DBC): + name: str + + # NOTE (mristin): + # The optional property mirrors ``Referable.ID_short`` in aas-core-meta V3.2, + # see :func:`nicknames_are_unique`. + nickname: Optional[str] + + def __init__(self, name: str, nickname: Optional[str] = None) -> None: + self.name = name + self.nickname = nickname + + +class Some_item(Item, DBC): + def __init__(self, name: str, nickname: Optional[str] = None) -> None: + Item.__init__(self, name, nickname) + + +class Special_item(Some_item, DBC): + level: int + + def __init__(self, name: str, level: int, nickname: Optional[str] = None) -> None: + Some_item.__init__(self, name, nickname) + self.level = level + + +@verification +def names_are_unique(items: Sequence[Item]) -> bool: + """ + Check that the names of the :paramref:`items` are unique. + + The :paramref:`items` are a generic sequence, so the sequences of + the descendants of :class:`Item` can be passed without a copy. + """ + name_set: Set[str] = set() + for item in items: + if item.name in name_set: + return False + + name_set.add(item.name) + + return True + + +@verification +def names_are_non_empty(items: Sequence[Item]) -> bool: + """Iterate over a generic sequence in a generator.""" + return all(len(item.name) > 0 for item in items) + + +@verification +def some_special_item_has_level(items: Sequence[Item]) -> bool: + """Narrow the items of a generic sequence in a generator.""" + return any(isinstance(item, Special_item) and item.level > 0 for item in items) + + +@verification +def first_is_special(items: Sequence[Item]) -> bool: + """Index a generic sequence, and narrow the item.""" + if len(items) == 0: + return False + + first = items[0] + if isinstance(first, Special_item): + return first.level > 0 + + return False + + +@verification +def nicknames_are_unique(items: Sequence[Item]) -> bool: + """ + Check that the nicknames of the :paramref:`items` are unique, if specified. + + This mirrors the body of ``ID_shorts_are_unique`` in aas-core-meta V3.2: + a local variable takes an optional property of an item of a generic sequence, + and the loop continues if it is None. + """ + nickname_set: Set[str] = set() + for item in items: + nickname = item.nickname + if nickname is None: + continue + + if nickname in nickname_set: + return False + + nickname_set.add(nickname) + + return True + + +@verification +def levels_of_special_items_are_positive(items: Sequence[Item]) -> bool: + """ + Narrow the loop variable over a generic sequence in a for-loop. + + The loop variable is of the type parameter in Go, so the down-cast needs to + go through ``any(...)``, and of the template parameter in C++. + """ + for item in items: + if isinstance(item, Special_item): + if item.level <= 0: + return False + + return True + + +@verification +def last_is_special(items: Sequence[Item]) -> bool: + """ + Narrow an immutable local variable which holds an item of a generic sequence. + + In contrast to :func:`first_is_special`, the local variable is annotated, so + it is of the parent class instead of the type parameter. Hence, Go down-casts + it without ``any(...)``, and C++ binds the item to a constant reference to + the shared pointer of the parent class. + """ + if len(items) == 0: + return False + + last: Final[Item] = items[len(items) - 1] + if isinstance(last, Special_item): + return last.level > 0 + + return False + + +@verification +def names_are_disjoint( + items: Sequence[Item], other_items: Sequence[Some_item], shared_name: str +) -> bool: + """ + Mix two generic sequences with an argument which is not generic. + + The names must differ, except for the :paramref:`shared_name`, which may repeat. + + Go declares two type parameters, and C++ two template parameters. The nested + generators iterate over two different generic sequences, so their variables + are of two different type parameters. + """ + return all( + all( + item.name != other_item.name or item.name == shared_name + for other_item in other_items + ) + for item in items + ) + + +@verification +def aliased_names_are_unique( + items: Sequence[Item], optional_items: Optional[Sequence[Item]] +) -> bool: + """ + Use the immutable aliases of the generic sequences. + + An immutable alias of the very same type shares the type annotation of + the aliased generic sequence, so it is a generic sequence itself. Go declares + the alias with the type parameter, C++ with the template parameter, while C# + and Java infer its type with ``var``. The ``alias_of_alias`` aliases an alias, + and the ``optional_alias`` aliases an optional generic sequence. + """ + alias: Final[Sequence[Item]] = items + alias_of_alias: Final[Sequence[Item]] = alias + optional_alias: Final[Optional[Sequence[Item]]] = optional_items + + for item in alias: + if isinstance(item, Special_item): + if item.level <= 0: + return False + + return ( + all(len(item.name) > 0 for item in alias_of_alias) + and names_are_unique(alias_of_alias) + and optional_names_are_unique(optional_alias) + and (optional_alias is None or names_are_unique(optional_alias)) + ) + + +@verification +def contains_some_item(some_item: Some_item, items: Sequence[Item]) -> bool: + """ + Look up an instance of a descendant in a generic sequence. + + Go compares the instances as ``any``'s, as the items are of the type parameter, + and C++ converts the member to the shared pointer of :class:`Item` so that + the raw pointers are comparable for any template parameter. + """ + return some_item in items + + +@verification +def items_overlap(items: Sequence[Item], other_items: Sequence[Some_item]) -> bool: + """ + Look up the items of a generic sequence in another generic sequence. + + The member and the items are of two different type parameters. + """ + return any(other_item in items for other_item in other_items) + + +@verification +def contains_special_item( + special_item: Special_item, items: Sequence[Special_item] +) -> bool: + """ + Look up an instance in a sequence which is not generic. + + The :class:`Special_item` has no descendants, so the :paramref:`items` are not + a generic sequence, but Go still compares the instances as ``any``'s. + """ + return special_item in items + + +@verification +def container_names_are_unique(container: "Container") -> bool: + """ + Pass the local variables on to generic sequences. + + The ``some_items`` is a read-only alias without an annotation, while + the ``all_some_items`` is an immutable local variable declared with + ``Final[...]``. Both hold the sequences of :class:`Some_item`, and are passed + on to the generic sequences of :class:`Item`, required as well as optional. + """ + some_items = container.some_items + all_some_items: Final[Sequence[Some_item]] = container.some_items + return ( + names_are_unique(some_items) + and names_are_unique(all_some_items) + and optional_names_are_unique(all_some_items) + ) + + +@verification +def optional_names_are_unique(items: Optional[Sequence[Item]]) -> bool: + """Pass an optional generic sequence on to a generic sequence.""" + return items is None or names_are_unique(items) + + +@verification +def some_names_are_unique( + some_items: Sequence[Some_item], special_items: Optional[Sequence[Special_item]] +) -> bool: + """ + Pass the generic sequences of the descendants on. + + The :paramref:`some_items` are passed on as a required and as an optional + sequence of the parent class, while the :paramref:`special_items` are passed on + as an optional sequence and, once narrowed, as a required sequence of the parent + class of the parent class. + """ + return ( + names_are_unique(some_items) + and optional_names_are_unique(some_items) + and optional_names_are_unique(special_items) + and optional_names_are_unique(None) + and (special_items is None or names_are_unique(special_items)) + ) + + +@verification +def levels_are_positive(items: Sequence[Special_item]) -> bool: + """ + Check that the levels are positive. + + The :class:`Special_item` has no descendants, so the :paramref:`items` are not + a generic sequence. + """ + return all(item.level > 0 for item in items) + + +@invariant( + lambda self: names_are_unique(self.items), + "Names of the items must be unique.", +) +@invariant( + lambda self: some_names_are_unique(self.some_items, self.special_items), + "Names of some items and of special items must be unique.", +) +@invariant( + lambda self: names_are_non_empty(self.some_items), + "Names of some items must be non-empty.", +) +@invariant( + lambda self: not some_special_item_has_level(self.items) + or first_is_special(self.items), + "If there is a special item with level, the first item must be special.", +) +@invariant( + lambda self: not (self.special_items is not None) + or levels_are_positive(self.special_items), + "Levels of the special items must be positive.", +) +@invariant( + lambda self: not (self.special_items is not None) + or first_is_special(self.special_items), + "The first special item must be special.", +) +@invariant( + lambda self: optional_names_are_unique(self.special_items), + "Names of the special items must be unique.", +) +@invariant( + lambda self: self.count_items(self.items) == len(self.items), + "Count of the items must match the number of the items.", +) +@invariant( + lambda self: nicknames_are_unique(self.some_items), + "Nicknames of some items must be unique.", +) +@invariant( + lambda self: levels_of_special_items_are_positive(self.items), + "Levels of the special items among the items must be positive.", +) +@invariant( + lambda self: not some_special_item_has_level(self.items) + or last_is_special(self.items), + "If there is a special item with level, the last item must be special.", +) +@invariant( + lambda self: names_are_disjoint(self.items, self.some_items, "x"), + "Names of the items and of some items must be disjoint.", +) +@invariant( + lambda self: aliased_names_are_unique(self.some_items, self.special_items), + "Names of some items and of special items must be unique through aliases.", +) +@invariant( + lambda self: all( + contains_some_item(some_item, self.items) for some_item in self.some_items + ), + "Some items must be among the items.", +) +@invariant( + lambda self: len(self.some_items) == 0 + or items_overlap(self.items, self.some_items), + "If there are some items, they must overlap with the items.", +) +@invariant( + lambda self: not (self.special_items is not None) + or all( + contains_special_item(special_item, self.special_items) + for special_item in self.special_items + ), + "Each special item must be among the special items.", +) +class Container(DBC): + items: List[Item] + some_items: List[Some_item] + special_items: Optional[List[Special_item]] + + @non_mutating + def count_items(self, items: Sequence[Item]) -> int: + """ + Count the :paramref:`items`. + + The arguments of the methods are never generic, so only the sequences of + :class:`Item` can be passed to it, but not the sequences of + the descendants. + """ + return len(items) + + def __init__( + self, + items: List[Item], + some_items: List[Some_item], + special_items: Optional[List[Special_item]] = None, + ) -> None: + self.items = items + self.some_items = some_items + self.special_items = special_items + + +# NOTE (mristin): +# We pass the container as a property, since the C++ invariants hold ``self`` as +# a raw pointer, which can not be passed on to a verification function expecting +# a shared pointer. +@invariant( + lambda self: container_names_are_unique(self.container), + "Names of some items of the container must be unique.", +) +class Wrapper(DBC): + container: Container + + def __init__(self, container: Container) -> None: + self.container = container + + +__version__ = "dummy" +__xml_namespace__ = "https://dummy.com" diff --git a/dev/test_data/intermediate/common_meta_models/variance_of_containers/expected_symbol_table.txt b/dev/test_data/intermediate/common_meta_models/variance_of_containers/expected_symbol_table.txt new file mode 100644 index 000000000..577aed1c6 --- /dev/null +++ b/dev/test_data/intermediate/common_meta_models/variance_of_containers/expected_symbol_table.txt @@ -0,0 +1,1637 @@ +SymbolTable( + our_types=[ + AbstractClass( + name='Item', + inheritances=[], + inheritance_id_set=..., + ancestors=[], + ancestor_id_set=..., + interface=Interface( + base='Reference to AbstractClass Item', + name='Item', + inheritances=[], + implementers=[ + 'Reference to ConcreteClass Some_item', + 'Reference to ConcreteClass Special_item'], + properties=[ + Property( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='name', + xml_name='name', + parsed=...), + Property( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='nickname', + xml_name='nickname', + parsed=...)], + signatures=[], + description=None, + parsed=..., + properties_by_name=..., + property_id_set=...), + descendant_id_set=..., + descendants=[ + 'Reference to ConcreteClass Some_item', + 'Reference to ConcreteClass Special_item'], + concrete_descendant_id_set=..., + concrete_descendants=[ + 'Reference to ConcreteClass Some_item', + 'Reference to ConcreteClass Special_item'], + properties=[ + Property( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='name', + xml_name='name', + parsed=...), + Property( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='nickname', + xml_name='nickname', + parsed=...)], + methods=[], + constructor=Constructor( + name='__init__', + arguments=[ + Argument( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + default=DefaultPrimitive( + value=None, + parsed=...), + mutable=False, + parsed=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=..., + statements=[ + textwrap.dedent("""\ + AssignArgument( + name='name', + argument='name', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='nickname', + argument='nickname', + default=None)""")], + inlined_statements=[ + textwrap.dedent("""\ + AssignArgument( + name='name', + argument='name', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='nickname', + argument='nickname', + default=None)""")]), + invariants=[], + serialization=Serialization( + with_model_type=True), + description=None, + parsed=..., + properties_by_name=..., + property_id_set=..., + methods_by_name=..., + method_id_set=..., + invariant_id_set=...), + ConcreteClass( + name='Some_item', + inheritances=[ + 'Reference to AbstractClass Item'], + inheritance_id_set=..., + ancestors=[ + 'Reference to AbstractClass Item'], + ancestor_id_set=..., + interface=Interface( + base='Reference to ConcreteClass Some_item', + name='Some_item', + inheritances=[ + 'Reference to Interface Item'], + implementers=[ + 'Reference to ConcreteClass Special_item', + 'Reference to ConcreteClass Some_item'], + properties=[], + signatures=[], + description=None, + parsed=..., + properties_by_name=..., + property_id_set=...), + descendant_id_set=..., + descendants=[ + 'Reference to ConcreteClass Special_item'], + concrete_descendant_id_set=..., + concrete_descendants=[ + 'Reference to ConcreteClass Special_item'], + properties=[ + Property( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='name', + xml_name='name', + parsed=...), + Property( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='nickname', + xml_name='nickname', + parsed=...)], + methods=[], + constructor=Constructor( + name='__init__', + arguments=[ + Argument( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + default=DefaultPrimitive( + value=None, + parsed=...), + mutable=False, + parsed=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=..., + statements=[ + textwrap.dedent("""\ + CallSuperConstructor( + super_name='Item')""")], + inlined_statements=[ + textwrap.dedent("""\ + AssignArgument( + name='name', + argument='name', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='nickname', + argument='nickname', + default=None)""")]), + invariants=[], + serialization=Serialization( + with_model_type=True), + description=None, + parsed=..., + properties_by_name=..., + property_id_set=..., + methods_by_name=..., + method_id_set=..., + invariant_id_set=...), + ConcreteClass( + name='Special_item', + inheritances=[ + 'Reference to ConcreteClass Some_item'], + inheritance_id_set=..., + ancestors=[ + 'Reference to AbstractClass Item', + 'Reference to ConcreteClass Some_item'], + ancestor_id_set=..., + interface=None, + descendant_id_set=..., + descendants=[], + concrete_descendant_id_set=..., + concrete_descendants=[], + properties=[ + Property( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='name', + xml_name='name', + parsed=...), + Property( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to AbstractClass Item', + json_name='nickname', + xml_name='nickname', + parsed=...), + Property( + name='level', + type_annotation=PrimitiveTypeAnnotation( + a_type='INT', + parsed=...), + description=None, + specified_for='Reference to ConcreteClass Special_item', + json_name='level', + xml_name='level', + parsed=...)], + methods=[], + constructor=Constructor( + name='__init__', + arguments=[ + Argument( + name='name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='level', + type_annotation=PrimitiveTypeAnnotation( + a_type='INT', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='nickname', + type_annotation=OptionalTypeAnnotation( + value=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + parsed=...), + default=DefaultPrimitive( + value=None, + parsed=...), + mutable=False, + parsed=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=..., + statements=[ + textwrap.dedent("""\ + CallSuperConstructor( + super_name='Some_item')"""), + textwrap.dedent("""\ + AssignArgument( + name='level', + argument='level', + default=None)""")], + inlined_statements=[ + textwrap.dedent("""\ + AssignArgument( + name='name', + argument='name', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='nickname', + argument='nickname', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='level', + argument='level', + default=None)""")]), + invariants=[], + serialization=Serialization( + with_model_type=True), + description=None, + parsed=..., + properties_by_name=..., + property_id_set=..., + methods_by_name=..., + method_id_set=..., + invariant_id_set=...), + ConcreteClass( + name='Container', + inheritances=[], + inheritance_id_set=..., + ancestors=[], + ancestor_id_set=..., + interface=None, + descendant_id_set=..., + descendants=[], + concrete_descendant_id_set=..., + concrete_descendants=[], + properties=[ + Property( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to ConcreteClass Container', + json_name='items', + xml_name='items', + parsed=...), + Property( + name='some_items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + parsed=...), + description=None, + specified_for='Reference to ConcreteClass Container', + json_name='someItems', + xml_name='someItems', + parsed=...), + Property( + name='special_items', + type_annotation=OptionalTypeAnnotation( + value=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + parsed=...), + parsed=...), + description=None, + specified_for='Reference to ConcreteClass Container', + json_name='specialItems', + xml_name='specialItems', + parsed=...)], + methods=[ + UnderstoodMethod( + name='count_items', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='INT', + parsed=...), + description=DescriptionOfSignature( + summary='Count the items.', + remarks=[ + textwrap.dedent("""\ + The arguments of the methods are never generic, so only the sequences of + Item can be passed to it, but not the sequences of + the descendants.""")], + arguments_by_name=[], + returns=None, + parsed=...), + specified_for='Reference to ConcreteClass Container', + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=True, + parsed=..., + body=[ + textwrap.dedent("""\ + Return( + value=FunctionCall( + name='len', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...), + original_node=...)""")], + arguments_by_name=...)], + constructor=Constructor( + name='__init__', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=True, + parsed=...), + Argument( + name='some_items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + parsed=...), + default=None, + mutable=True, + parsed=...), + Argument( + name='special_items', + type_annotation=OptionalTypeAnnotation( + value=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + parsed=...), + parsed=...), + default=DefaultPrimitive( + value=None, + parsed=...), + mutable=True, + parsed=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=..., + statements=[ + textwrap.dedent("""\ + AssignArgument( + name='items', + argument='items', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='some_items', + argument='some_items', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='special_items', + argument='special_items', + default=None)""")], + inlined_statements=[ + textwrap.dedent("""\ + AssignArgument( + name='items', + argument='items', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='some_items', + argument='some_items', + default=None)"""), + textwrap.dedent("""\ + AssignArgument( + name='special_items', + argument='special_items', + default=None)""")]), + invariants=[ + Invariant( + description='Each special item must be among the special items.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=All( + generator=ForEach( + variable=Name( + identifier='special_item', + original_node=...), + iteration=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + condition=FunctionCall( + name='contains_special_item', + args=[ + Name( + identifier='special_item', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='If there are some items, they must overlap with the items.', + body=textwrap.dedent("""\ + Or( + values=[ + Comparison( + left=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...), + op='EQ', + right=Constant( + value=0, + original_node=...), + original_node=...), + FunctionCall( + name='items_overlap', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Some items must be among the items.', + body=textwrap.dedent("""\ + All( + generator=ForEach( + variable=Name( + identifier='some_item', + original_node=...), + iteration=Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + original_node=...), + condition=FunctionCall( + name='contains_some_item', + args=[ + Name( + identifier='some_item', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of some items and of special items must be unique through aliases.', + body=textwrap.dedent("""\ + FunctionCall( + name='aliased_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of the items and of some items must be disjoint.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_disjoint', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Constant( + value='x', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='If there is a special item with level, the last item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=FunctionCall( + name='some_special_item_has_level', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + consequent=FunctionCall( + name='last_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Levels of the special items among the items must be positive.', + body=textwrap.dedent("""\ + FunctionCall( + name='levels_of_special_items_are_positive', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Nicknames of some items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='nicknames_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Count of the items must match the number of the items.', + body=textwrap.dedent("""\ + Comparison( + left=MethodCall( + member=Member( + instance=Name( + identifier='self', + original_node=...), + name='count_items', + original_node=...), + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + op='EQ', + right=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of the special items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='optional_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='The first special item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=FunctionCall( + name='first_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Levels of the special items must be positive.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=FunctionCall( + name='levels_are_positive', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='If there is a special item with level, the first item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=FunctionCall( + name='some_special_item_has_level', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + consequent=FunctionCall( + name='first_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of some items must be non-empty.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_non_empty', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of some items and of special items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='some_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...), + Invariant( + description='Names of the items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Container', + parsed=...)], + serialization=Serialization( + with_model_type=False), + description=None, + parsed=..., + properties_by_name=..., + property_id_set=..., + methods_by_name=..., + method_id_set=..., + invariant_id_set=...), + ConcreteClass( + name='Wrapper', + inheritances=[], + inheritance_id_set=..., + ancestors=[], + ancestor_id_set=..., + interface=None, + descendant_id_set=..., + descendants=[], + concrete_descendant_id_set=..., + concrete_descendants=[], + properties=[ + Property( + name='container', + type_annotation=OurTypeAnnotation( + our_type='Reference to our type Container', + parsed=...), + description=None, + specified_for='Reference to ConcreteClass Wrapper', + json_name='container', + xml_name='container', + parsed=...)], + methods=[], + constructor=Constructor( + name='__init__', + arguments=[ + Argument( + name='container', + type_annotation=OurTypeAnnotation( + our_type='Reference to our type Container', + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=..., + statements=[ + textwrap.dedent("""\ + AssignArgument( + name='container', + argument='container', + default=None)""")], + inlined_statements=[ + textwrap.dedent("""\ + AssignArgument( + name='container', + argument='container', + default=None)""")]), + invariants=[ + Invariant( + description='Names of some items of the container must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='container_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='container', + original_node=...)], + original_node=...)"""), + specified_for='Reference to ConcreteClass Wrapper', + parsed=...)], + serialization=Serialization( + with_model_type=False), + description=None, + parsed=..., + properties_by_name=..., + property_id_set=..., + methods_by_name=..., + method_id_set=..., + invariant_id_set=...)], + our_types_topologically_sorted=[ + 'Reference to our type Container', + 'Reference to our type Item', + 'Reference to our type Some_item', + 'Reference to our type Special_item', + 'Reference to our type Wrapper'], + enumerations=[], + constrained_primitives=[], + classes=[ + 'Reference to our type Item', + 'Reference to our type Some_item', + 'Reference to our type Special_item', + 'Reference to our type Container', + 'Reference to our type Wrapper'], + concrete_classes=[ + 'Reference to our type Some_item', + 'Reference to our type Special_item', + 'Reference to our type Container', + 'Reference to our type Wrapper'], + named_unions=[], + constants=[], + constants_by_name=..., + verification_functions=[ + TranspilableVerification( + name='names_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Check that the names of the items are unique.', + remarks=[ + textwrap.dedent("""\ + The items are a generic sequence, so the sequences of + the descendants of Item can be passed without a copy.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='names_are_non_empty', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Iterate over a generic sequence in a generator.', + remarks=[], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='some_special_item_has_level', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Narrow the items of a generic sequence in a generator.', + remarks=[], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='first_is_special', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Index a generic sequence, and narrow the item.', + remarks=[], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='nicknames_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Check that the nicknames of the items are unique, if specified.', + remarks=[ + textwrap.dedent("""\ + This mirrors the body of ID_shorts_are_unique in aas-core-meta V3.2: + a local variable takes an optional property of an item of a generic sequence, + and the loop continues if it is None.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='levels_of_special_items_are_positive', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Narrow the loop variable over a generic sequence in a for-loop.', + remarks=[ + textwrap.dedent("""\ + The loop variable is of the type parameter in Go, so the down-cast needs to + go through any(...), and of the template parameter in C++.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='last_is_special', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Narrow an immutable local variable which holds an item of a generic sequence.', + remarks=[ + textwrap.dedent("""\ + In contrast to first_is_special, the local variable is annotated, so + it is of the parent class instead of the type parameter. Hence, Go down-casts + it without any(...), and C++ binds the item to a constant reference to + the shared pointer of the parent class.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='names_are_disjoint', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='other_items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='shared_name', + type_annotation=PrimitiveTypeAnnotation( + a_type='STR', + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Mix two generic sequences with an argument which is not generic.', + remarks=[ + 'The names must differ, except for the shared_name, which may repeat.', + textwrap.dedent("""\ + Go declares two type parameters, and C++ two template parameters. The nested + generators iterate over two different generic sequences, so their variables + are of two different type parameters.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='aliased_names_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='optional_items', + type_annotation=OptionalTypeAnnotation( + value=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Use the immutable aliases of the generic sequences.', + remarks=[ + textwrap.dedent("""\ + An immutable alias of the very same type shares the type annotation of + the aliased generic sequence, so it is a generic sequence itself. Go declares + the alias with the type parameter, C++ with the template parameter, while C# + and Java infer its type with var. The alias_of_alias aliases an alias, + and the optional_alias aliases an optional generic sequence.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='contains_some_item', + visibility='PUBLIC', + arguments=[ + Argument( + name='some_item', + type_annotation=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Look up an instance of a descendant in a generic sequence.', + remarks=[ + textwrap.dedent("""\ + Go compares the instances as any's, as the items are of the type parameter, + and C++ converts the member to the shared pointer of Item so that + the raw pointers are comparable for any template parameter.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='items_overlap', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='other_items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Look up the items of a generic sequence in another generic sequence.', + remarks=[ + 'The member and the items are of two different type parameters.'], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='contains_special_item', + visibility='PUBLIC', + arguments=[ + Argument( + name='special_item', + type_annotation=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Look up an instance in a sequence which is not generic.', + remarks=[ + textwrap.dedent("""\ + The Special_item has no descendants, so the items are not + a generic sequence, but Go still compares the instances as any's.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='container_names_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='container', + type_annotation=OurTypeAnnotation( + our_type='Reference to our type Container', + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Pass the local variables on to generic sequences.', + remarks=[ + textwrap.dedent("""\ + The some_items is a read-only alias without an annotation, while + the all_some_items is an immutable local variable declared with + Final[...]. Both hold the sequences of Some_item, and are passed + on to the generic sequences of Item, required as well as optional.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='optional_names_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=OptionalTypeAnnotation( + value=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Item', + parsed=...), + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Pass an optional generic sequence on to a generic sequence.', + remarks=[], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='some_names_are_unique', + visibility='PUBLIC', + arguments=[ + Argument( + name='some_items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Some_item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...), + Argument( + name='special_items', + type_annotation=OptionalTypeAnnotation( + value=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Pass the generic sequences of the descendants on.', + remarks=[ + textwrap.dedent("""\ + The some_items are passed on as a required and as an optional + sequence of the parent class, while the special_items are passed on + as an optional sequence and, once narrowed, as a required sequence of the parent + class of the parent class.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...), + TranspilableVerification( + name='levels_are_positive', + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=ListTypeAnnotation( + items=OurTypeAnnotation( + our_type='Reference to our type Special_item', + parsed=...), + parsed=...), + default=None, + mutable=False, + parsed=...)], + returns=PrimitiveTypeAnnotation( + a_type='BOOL', + parsed=...), + description=DescriptionOfSignature( + summary='Check that the levels are positive.', + remarks=[ + textwrap.dedent("""\ + The Special_item has no descendants, so the items are not + a generic sequence.""")], + arguments_by_name=[], + returns=None, + parsed=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + parsed=..., + arguments_by_name=...)], + verification_functions_by_name=..., + meta_model=MetaModel( + description=DescriptionOfMetaModel( + summary='Test the variance of the containers passed as arguments.', + remarks=[ + textwrap.dedent("""\ + A read-only Sequence[...] argument of a verification function is generic if its + items are instances of a class with descendants, so that the sequences of + the descendants can be passed to it without a copy. The arguments of the methods + and the mutable List[...] arguments remain invariant.""")], + constraints_by_identifier=[], + parsed=...), + version='dummy', + xml_namespace='https://dummy.com')) \ No newline at end of file diff --git a/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/include/dummy/verification.hpp index d46b61dbb..57bd5c1ee 100644 --- a/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/include/dummy/verification.hpp +++ b/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/include/dummy/verification.hpp @@ -5,13 +5,18 @@ // Do NOT edit or append. #include "dummy/common.hpp" +#include "dummy/constants.hpp" #include "dummy/iteration.hpp" #include "dummy/pattern.hpp" +#include "dummy/stringification.hpp" #include "dummy/types.hpp" #pragma warning(push, 0) +#include #include +#include #include +#include #pragma warning(pop) namespace dummy { @@ -216,9 +221,14 @@ class RecursiveVerification : public IVerification { // region Verification functions /// \brief Check an optional class declared with `None` and assigned in a loop. +/// +/// \tparam ParentsT type of the items of \p parents, types::IParent or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ParentsT +> bool FirstChildAIsSmall( const std::vector< - std::shared_ptr + std::shared_ptr >& parents ); @@ -228,10 +238,15 @@ bool TextIsAllowedUnlessLong( ); /// \brief Check a non-optional variable declared wider than its value. +/// +/// \tparam ParentsT type of the items of \p parents, types::IParent or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ParentsT +> bool LastChildBIsNotZero( const std::shared_ptr& parent, const std::vector< - std::shared_ptr + std::shared_ptr >& parents ); @@ -326,6 +341,60 @@ bool NoneIsShortAndTextIsShort( // endregion Verification functions +// region Definitions of generic verification functions + +template< + typename ParentsT +> +bool FirstChildAIsSmall( + const std::vector< + std::shared_ptr + >& parents +) { + static_assert( + std::is_base_of::value, + "Expected the items of parents to be instances of types::IParent" + ); + common::optional > found = common::nullopt; + for (const std::shared_ptr& parent : parents) { + if (types::IsChildA(*parent)) { + found = std::dynamic_pointer_cast(parent); + break; + } + } + if (!(found.has_value())) { + return true; + } + return (*found)->a_only() < 100; +} + +template< + typename ParentsT +> +bool LastChildBIsNotZero( + const std::shared_ptr& parent, + const std::vector< + std::shared_ptr + >& parents +) { + static_assert( + std::is_base_of::value, + "Expected the items of parents to be instances of types::IParent" + ); + std::shared_ptr last = parent; + for (const std::shared_ptr& other : parents) { + last = other; + } + if (types::IsChildB(*last)) { + return ( + std::dynamic_pointer_cast(last)->b_only() != 0 + ); + } + return true; +} + +// endregion Definitions of generic verification functions + // region Verification of constrained primitives /** diff --git a/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/src/verification.cpp index 2eb24080f..067a15066 100644 --- a/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/src/verification.cpp +++ b/dev/test_data/main/cpp/expected/annotated_assignments/expected_output/src/verification.cpp @@ -39,24 +39,6 @@ Error::Error( // region Verification functions -bool FirstChildAIsSmall( - const std::vector< - std::shared_ptr - >& parents -) { - common::optional > found = common::nullopt; - for (const std::shared_ptr& parent : parents) { - if (types::IsChildA(*parent)) { - found = std::dynamic_pointer_cast(parent); - break; - } - } - if (!(found.has_value())) { - return true; - } - return (*found)->a_only() < 100; -} - bool TextIsAllowedUnlessLong( const common::optional& text ) { @@ -94,24 +76,6 @@ bool TextIsAllowedUnlessLong( ); } -bool LastChildBIsNotZero( - const std::shared_ptr& parent, - const std::vector< - std::shared_ptr - >& parents -) { - std::shared_ptr last = parent; - for (const std::shared_ptr& other : parents) { - last = other; - } - if (types::IsChildB(*last)) { - return ( - std::dynamic_pointer_cast(last)->b_only() != 0 - ); - } - return true; -} - bool ChildADeclarationsAgree( const std::shared_ptr& parent ) { diff --git a/dev/test_data/main/cpp/expected/if_statements/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/if_statements/expected_output/include/dummy/verification.hpp index 5c1a8dd3f..bedfafb04 100644 --- a/dev/test_data/main/cpp/expected/if_statements/expected_output/include/dummy/verification.hpp +++ b/dev/test_data/main/cpp/expected/if_statements/expected_output/include/dummy/verification.hpp @@ -5,12 +5,17 @@ // Do NOT edit or append. #include "dummy/common.hpp" +#include "dummy/constants.hpp" #include "dummy/iteration.hpp" #include "dummy/pattern.hpp" +#include "dummy/stringification.hpp" #include "dummy/types.hpp" #pragma warning(push, 0) +#include #include +#include +#include #pragma warning(pop) namespace dummy { @@ -297,16 +302,26 @@ bool NarrowingAfterTheOnlyCompletingBranch( ); /// \brief Check the narrowing after the `continue` and the early return in a loop. +/// +/// \tparam ParentsT type of the items of \p parents, types::IParent or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ParentsT +> bool ChildAsHaveTexts( const std::vector< - std::shared_ptr + std::shared_ptr >& parents ); /// \brief Check the narrowing after the `continue` in a loop with a `break`. +/// +/// \tparam ParentsT type of the items of \p parents, types::IParent or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ParentsT +> bool TextsBeforeContainerAreShort( const std::vector< - std::shared_ptr + std::shared_ptr >& parents ); @@ -316,10 +331,15 @@ bool TextOrDefaultIsShort( ); /// \brief Check the narrowing of a variable to a class by the assigned value. +/// +/// \tparam ParentsT type of the items of \p parents, types::IParent or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ParentsT +> bool LastChildAIsSmall( const std::shared_ptr& parent, const std::vector< - std::shared_ptr + std::shared_ptr >& parents ); @@ -330,6 +350,88 @@ bool HasMarkerInTree( // endregion Verification functions +// region Definitions of generic verification functions + +template< + typename ParentsT +> +bool ChildAsHaveTexts( + const std::vector< + std::shared_ptr + >& parents +) { + static_assert( + std::is_base_of::value, + "Expected the items of parents to be instances of types::IParent" + ); + for (const std::shared_ptr& parent : parents) { + if (!types::IsChildA(*parent)) { + continue; + } + if (!(std::dynamic_pointer_cast(parent)->optional_text().has_value())) { + return false; + } + if (common::LenStr((*(std::dynamic_pointer_cast(parent)->optional_text()))) < 1) { + return false; + } + } + return true; +} + +template< + typename ParentsT +> +bool TextsBeforeContainerAreShort( + const std::vector< + std::shared_ptr + >& parents +) { + static_assert( + std::is_base_of::value, + "Expected the items of parents to be instances of types::IParent" + ); + int64_t total = 0; + for (const std::shared_ptr& parent : parents) { + if (types::IsContainer(*parent)) { + break; + } + if (!(parent->optional_text().has_value())) { + continue; + } + total = ( + total + static_cast(common::LenStr((*(parent->optional_text())))) + ); + } + return total < 20; +} + +template< + typename ParentsT +> +bool LastChildAIsSmall( + const std::shared_ptr& parent, + const std::vector< + std::shared_ptr + >& parents +) { + static_assert( + std::is_base_of::value, + "Expected the items of parents to be instances of types::IParent" + ); + auto last = parent; + for (const std::shared_ptr& other : parents) { + if (types::IsChildA(*other)) { + last = std::dynamic_pointer_cast(other); + if (std::dynamic_pointer_cast(last)->a_only() >= 1000) { + return false; + } + } + } + return true; +} + +// endregion Definitions of generic verification functions + } // namespace verification /**@}*/ diff --git a/dev/test_data/main/cpp/expected/if_statements/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/if_statements/expected_output/src/verification.cpp index 2a73dca0c..38f3271d1 100644 --- a/dev/test_data/main/cpp/expected/if_statements/expected_output/src/verification.cpp +++ b/dev/test_data/main/cpp/expected/if_statements/expected_output/src/verification.cpp @@ -251,45 +251,6 @@ bool NarrowingAfterTheOnlyCompletingBranch( ); } -bool ChildAsHaveTexts( - const std::vector< - std::shared_ptr - >& parents -) { - for (const std::shared_ptr& parent : parents) { - if (!types::IsChildA(*parent)) { - continue; - } - if (!(std::dynamic_pointer_cast(parent)->optional_text().has_value())) { - return false; - } - if (common::LenStr((*(std::dynamic_pointer_cast(parent)->optional_text()))) < 1) { - return false; - } - } - return true; -} - -bool TextsBeforeContainerAreShort( - const std::vector< - std::shared_ptr - >& parents -) { - int64_t total = 0; - for (const std::shared_ptr& parent : parents) { - if (types::IsContainer(*parent)) { - break; - } - if (!(parent->optional_text().has_value())) { - continue; - } - total = ( - total + static_cast(common::LenStr((*(parent->optional_text())))) - ); - } - return total < 20; -} - bool TextOrDefaultIsShort( const std::shared_ptr& parent ) { @@ -300,24 +261,6 @@ bool TextOrDefaultIsShort( return common::LenStr((*text)) < 10; } -bool LastChildAIsSmall( - const std::shared_ptr& parent, - const std::vector< - std::shared_ptr - >& parents -) { - auto last = parent; - for (const std::shared_ptr& other : parents) { - if (types::IsChildA(*other)) { - last = std::dynamic_pointer_cast(other); - if (std::dynamic_pointer_cast(last)->a_only() >= 1000) { - return false; - } - } - } - return true; -} - bool HasMarkerInTree( const std::shared_ptr& parent ) { diff --git a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp index 4b436c3b7..3152a059b 100644 --- a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp +++ b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/include/dummy/verification.hpp @@ -5,12 +5,17 @@ // Do NOT edit or append. #include "dummy/common.hpp" +#include "dummy/constants.hpp" #include "dummy/iteration.hpp" #include "dummy/pattern.hpp" +#include "dummy/stringification.hpp" #include "dummy/types.hpp" #pragma warning(push, 0) +#include #include +#include +#include #pragma warning(pop) namespace dummy { @@ -235,11 +240,16 @@ std::vector< /// /// The appended items are narrowed down to types::ISomeItem, while the list /// holds types::IAbstractItem's. +/// +/// \tparam ItemsT type of the items of \p items, types::IAbstractItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> std::vector< std::shared_ptr > SomeItemsOf( const std::vector< - std::shared_ptr + std::shared_ptr >& items ); @@ -266,6 +276,35 @@ bool IsInLocalCopyOfSimples( // endregion Verification functions +// region Definitions of generic verification functions + +template< + typename ItemsT +> +std::vector< + std::shared_ptr +> SomeItemsOf( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IAbstractItem" + ); + std::vector > result = ( + std::vector >() + ); + for (const std::shared_ptr& item : items) { + if (types::IsSomeItem(*item)) { + result.push_back(std::dynamic_pointer_cast(item)); + } + } + return result; +} + +// endregion Definitions of generic verification functions + } // namespace verification /**@}*/ diff --git a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp index 226711371..ac426bb53 100644 --- a/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp +++ b/dev/test_data/main/cpp/expected/list_of_classes/expected_output/src/verification.cpp @@ -68,24 +68,6 @@ std::vector< return result; } -std::vector< - std::shared_ptr -> SomeItemsOf( - const std::vector< - std::shared_ptr - >& items -) { - std::vector > result = ( - std::vector >() - ); - for (const std::shared_ptr& item : items) { - if (types::IsSomeItem(*item)) { - result.push_back(std::dynamic_pointer_cast(item)); - } - } - return result; -} - bool IsInSimples( const std::shared_ptr& simple, const std::vector< diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/common.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/common.hpp new file mode 100644 index 000000000..867ddb01b --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/common.hpp @@ -0,0 +1,10196 @@ +#ifndef DUMMY_GUARD_ +#define DUMMY_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +// NOTE (mristin): +// See: https://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +// NOTE (mristin): +// Standard library provides std::optional in C++17 and above. +#pragma warning(push, 0) +#include +#pragma warning(pop) +#else +// NOTE (mristin): +// We rely on https://github.com/TartanLlama/optional for optional structure. +#pragma warning(push, 0) +#include +#pragma warning(pop) +#endif + +// NOTE (mristin): +// We check for the version above C++20 as there is no C++23 literal yet, and +// std::expected is available only in C++23. +// See: https://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler +// and: http://eel.is/c++draft/cpp.predefined#1.1 +#if ((defined(_MSVC_LANG) && _MSVC_LANG > 202002L) || __cplusplus > 202002L) +// Standard library provides std::expected in C++23 and above. +#pragma warning(push, 0) +#include +#pragma warning(pop) +#else +// NOTE (mristin): +// We rely on https://github.com/TartanLlama/expected for expected structure. +#pragma warning(push, 0) +#include +#pragma warning(pop) +#endif + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup common Common functionality used throughout the library + * @{ + */ +namespace common { + +// Please keep in sync with the preprocessing directives above in the include block. +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L) +// Standard library provides std::optional in C++17 and above. +using std::optional; +using std::nullopt; +using std::make_optional; +#else +using tl::optional; +using tl::nullopt; +using tl::make_optional; +#endif + +// Please keep in sync with the preprocessing directives above in the include block. +#if ((defined(_MSVC_LANG) && _MSVC_LANG > 202002L) || __cplusplus > 202002L) +using std::expected; +using std::unexpected; +using std::make_unexpected; +#else +using tl::expected; +using tl::unexpected; +using tl::make_unexpected; +#endif + +// Please keep in sync with the preprocessing directives above in the include block. +// Standard library provides std::make_unique in C++14 and above. +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) || __cplusplus >= 201402L) +using std::make_unique; +#else +// Inspired by: +// https://stackoverflow.com/questions/12547983/is-there-a-way-to-write-make-unique-in-vs2012 +template +std::unique_ptr make_unique() { + return std::unique_ptr( + new T() + ); +} + +template< + typename T, + typename A1 +> +std::unique_ptr make_unique( + A1&& a1 +) { + return std::unique_ptr( + new T( + std::forward(a1) + ) + ); +} + +template< + typename T, + typename A1, + typename A2 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10, + typename A11 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10, + A11&& a11 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10), + std::forward(a11) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10, + typename A11, + typename A12 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10, + A11&& a11, + A12&& a12 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10), + std::forward(a11), + std::forward(a12) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10, + typename A11, + typename A12, + typename A13 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10, + A11&& a11, + A12&& a12, + A13&& a13 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10), + std::forward(a11), + std::forward(a12), + std::forward(a13) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10, + typename A11, + typename A12, + typename A13, + typename A14 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10, + A11&& a11, + A12&& a12, + A13&& a13, + A14&& a14 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10), + std::forward(a11), + std::forward(a12), + std::forward(a13), + std::forward(a14) + ) + ); +} + +template< + typename T, + typename A1, + typename A2, + typename A3, + typename A4, + typename A5, + typename A6, + typename A7, + typename A8, + typename A9, + typename A10, + typename A11, + typename A12, + typename A13, + typename A14, + typename A15 +> +std::unique_ptr make_unique( + A1&& a1, + A2&& a2, + A3&& a3, + A4&& a4, + A5&& a5, + A6&& a6, + A7&& a7, + A8&& a8, + A9&& a9, + A10&& a10, + A11&& a11, + A12&& a12, + A13&& a13, + A14&& a14, + A15&& a15 +) { + return std::unique_ptr( + new T( + std::forward(a1), + std::forward(a2), + std::forward(a3), + std::forward(a4), + std::forward(a5), + std::forward(a6), + std::forward(a7), + std::forward(a8), + std::forward(a9), + std::forward(a10), + std::forward(a11), + std::forward(a12), + std::forward(a13), + std::forward(a14), + std::forward(a15) + ) + ); +} +#endif + +/** + * Concatenate 2 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \return a concatenation of the 2 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1 +); + +/** + * Concatenate 3 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \return a concatenation of the 3 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2 +); + +/** + * Concatenate 4 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \return a concatenation of the 4 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3 +); + +/** + * Concatenate 5 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \return a concatenation of the 5 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4 +); + +/** + * Concatenate 6 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \return a concatenation of the 6 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5 +); + +/** + * Concatenate 7 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \return a concatenation of the 7 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6 +); + +/** + * Concatenate 8 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \return a concatenation of the 8 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7 +); + +/** + * Concatenate 9 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \return a concatenation of the 9 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8 +); + +/** + * Concatenate 10 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \return a concatenation of the 10 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9 +); + +/** + * Concatenate 11 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \return a concatenation of the 11 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10 +); + +/** + * Concatenate 12 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \return a concatenation of the 12 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11 +); + +/** + * Concatenate 13 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \return a concatenation of the 13 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12 +); + +/** + * Concatenate 14 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \return a concatenation of the 14 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13 +); + +/** + * Concatenate 15 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \return a concatenation of the 15 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14 +); + +/** + * Concatenate 16 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \return a concatenation of the 16 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15 +); + +/** + * Concatenate 17 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \return a concatenation of the 17 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16 +); + +/** + * Concatenate 18 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \return a concatenation of the 18 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17 +); + +/** + * Concatenate 19 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \return a concatenation of the 19 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18 +); + +/** + * Concatenate 20 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \return a concatenation of the 20 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19 +); + +/** + * Concatenate 21 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \return a concatenation of the 21 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20 +); + +/** + * Concatenate 22 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \return a concatenation of the 22 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21 +); + +/** + * Concatenate 23 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \return a concatenation of the 23 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22 +); + +/** + * Concatenate 24 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \return a concatenation of the 24 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23 +); + +/** + * Concatenate 25 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \return a concatenation of the 25 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24 +); + +/** + * Concatenate 26 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \return a concatenation of the 26 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25 +); + +/** + * Concatenate 27 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \return a concatenation of the 27 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26 +); + +/** + * Concatenate 28 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \return a concatenation of the 28 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27 +); + +/** + * Concatenate 29 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \return a concatenation of the 29 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28 +); + +/** + * Concatenate 30 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \return a concatenation of the 30 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29 +); + +/** + * Concatenate 31 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \return a concatenation of the 31 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30 +); + +/** + * Concatenate 32 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \return a concatenation of the 32 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31 +); + +/** + * Concatenate 33 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \return a concatenation of the 33 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32 +); + +/** + * Concatenate 34 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \return a concatenation of the 34 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33 +); + +/** + * Concatenate 35 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \return a concatenation of the 35 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34 +); + +/** + * Concatenate 36 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \return a concatenation of the 36 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35 +); + +/** + * Concatenate 37 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \return a concatenation of the 37 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36 +); + +/** + * Concatenate 38 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \return a concatenation of the 38 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37 +); + +/** + * Concatenate 39 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \return a concatenation of the 39 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38 +); + +/** + * Concatenate 40 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \return a concatenation of the 40 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39 +); + +/** + * Concatenate 41 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \return a concatenation of the 41 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40 +); + +/** + * Concatenate 42 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \return a concatenation of the 42 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41 +); + +/** + * Concatenate 43 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \return a concatenation of the 43 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42 +); + +/** + * Concatenate 44 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \return a concatenation of the 44 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43 +); + +/** + * Concatenate 45 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \return a concatenation of the 45 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44 +); + +/** + * Concatenate 46 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \return a concatenation of the 46 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45 +); + +/** + * Concatenate 47 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \return a concatenation of the 47 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46 +); + +/** + * Concatenate 48 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \return a concatenation of the 48 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47 +); + +/** + * Concatenate 49 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \return a concatenation of the 49 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48 +); + +/** + * Concatenate 50 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \return a concatenation of the 50 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49 +); + +/** + * Concatenate 51 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \return a concatenation of the 51 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50 +); + +/** + * Concatenate 52 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \return a concatenation of the 52 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51 +); + +/** + * Concatenate 53 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \return a concatenation of the 53 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52 +); + +/** + * Concatenate 54 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \return a concatenation of the 54 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53 +); + +/** + * Concatenate 55 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \return a concatenation of the 55 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54 +); + +/** + * Concatenate 56 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \return a concatenation of the 56 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55 +); + +/** + * Concatenate 57 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \return a concatenation of the 57 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56 +); + +/** + * Concatenate 58 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \return a concatenation of the 58 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57 +); + +/** + * Concatenate 59 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \return a concatenation of the 59 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58 +); + +/** + * Concatenate 60 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \return a concatenation of the 60 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59 +); + +/** + * Concatenate 61 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \return a concatenation of the 61 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60 +); + +/** + * Concatenate 62 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \return a concatenation of the 62 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61 +); + +/** + * Concatenate 63 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \param part62 63rd part of the concatenation + * \return a concatenation of the 63 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61, + const std::string& part62 +); + +/** + * Concatenate 64 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \param part62 63rd part of the concatenation + * \param part63 64th part of the concatenation + * \return a concatenation of the 64 parts + */ +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61, + const std::string& part62, + const std::string& part63 +); + +/** + * Concatenate 2 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \return a concatenation of the 2 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1 +); + +/** + * Concatenate 3 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \return a concatenation of the 3 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2 +); + +/** + * Concatenate 4 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \return a concatenation of the 4 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3 +); + +/** + * Concatenate 5 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \return a concatenation of the 5 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4 +); + +/** + * Concatenate 6 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \return a concatenation of the 6 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5 +); + +/** + * Concatenate 7 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \return a concatenation of the 7 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6 +); + +/** + * Concatenate 8 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \return a concatenation of the 8 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7 +); + +/** + * Concatenate 9 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \return a concatenation of the 9 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8 +); + +/** + * Concatenate 10 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \return a concatenation of the 10 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9 +); + +/** + * Concatenate 11 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \return a concatenation of the 11 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10 +); + +/** + * Concatenate 12 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \return a concatenation of the 12 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11 +); + +/** + * Concatenate 13 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \return a concatenation of the 13 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12 +); + +/** + * Concatenate 14 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \return a concatenation of the 14 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13 +); + +/** + * Concatenate 15 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \return a concatenation of the 15 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14 +); + +/** + * Concatenate 16 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \return a concatenation of the 16 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15 +); + +/** + * Concatenate 17 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \return a concatenation of the 17 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16 +); + +/** + * Concatenate 18 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \return a concatenation of the 18 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17 +); + +/** + * Concatenate 19 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \return a concatenation of the 19 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18 +); + +/** + * Concatenate 20 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \return a concatenation of the 20 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19 +); + +/** + * Concatenate 21 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \return a concatenation of the 21 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20 +); + +/** + * Concatenate 22 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \return a concatenation of the 22 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21 +); + +/** + * Concatenate 23 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \return a concatenation of the 23 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22 +); + +/** + * Concatenate 24 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \return a concatenation of the 24 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23 +); + +/** + * Concatenate 25 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \return a concatenation of the 25 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24 +); + +/** + * Concatenate 26 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \return a concatenation of the 26 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25 +); + +/** + * Concatenate 27 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \return a concatenation of the 27 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26 +); + +/** + * Concatenate 28 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \return a concatenation of the 28 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27 +); + +/** + * Concatenate 29 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \return a concatenation of the 29 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28 +); + +/** + * Concatenate 30 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \return a concatenation of the 30 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29 +); + +/** + * Concatenate 31 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \return a concatenation of the 31 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30 +); + +/** + * Concatenate 32 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \return a concatenation of the 32 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31 +); + +/** + * Concatenate 33 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \return a concatenation of the 33 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32 +); + +/** + * Concatenate 34 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \return a concatenation of the 34 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33 +); + +/** + * Concatenate 35 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \return a concatenation of the 35 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34 +); + +/** + * Concatenate 36 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \return a concatenation of the 36 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35 +); + +/** + * Concatenate 37 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \return a concatenation of the 37 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36 +); + +/** + * Concatenate 38 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \return a concatenation of the 38 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37 +); + +/** + * Concatenate 39 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \return a concatenation of the 39 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38 +); + +/** + * Concatenate 40 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \return a concatenation of the 40 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39 +); + +/** + * Concatenate 41 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \return a concatenation of the 41 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40 +); + +/** + * Concatenate 42 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \return a concatenation of the 42 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41 +); + +/** + * Concatenate 43 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \return a concatenation of the 43 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42 +); + +/** + * Concatenate 44 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \return a concatenation of the 44 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43 +); + +/** + * Concatenate 45 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \return a concatenation of the 45 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44 +); + +/** + * Concatenate 46 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \return a concatenation of the 46 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45 +); + +/** + * Concatenate 47 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \return a concatenation of the 47 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46 +); + +/** + * Concatenate 48 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \return a concatenation of the 48 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47 +); + +/** + * Concatenate 49 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \return a concatenation of the 49 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48 +); + +/** + * Concatenate 50 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \return a concatenation of the 50 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49 +); + +/** + * Concatenate 51 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \return a concatenation of the 51 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50 +); + +/** + * Concatenate 52 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \return a concatenation of the 52 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51 +); + +/** + * Concatenate 53 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \return a concatenation of the 53 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52 +); + +/** + * Concatenate 54 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \return a concatenation of the 54 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53 +); + +/** + * Concatenate 55 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \return a concatenation of the 55 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54 +); + +/** + * Concatenate 56 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \return a concatenation of the 56 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55 +); + +/** + * Concatenate 57 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \return a concatenation of the 57 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56 +); + +/** + * Concatenate 58 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \return a concatenation of the 58 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57 +); + +/** + * Concatenate 59 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \return a concatenation of the 59 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58 +); + +/** + * Concatenate 60 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \return a concatenation of the 60 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59 +); + +/** + * Concatenate 61 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \return a concatenation of the 61 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60 +); + +/** + * Concatenate 62 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \return a concatenation of the 62 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61 +); + +/** + * Concatenate 63 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \param part62 63rd part of the concatenation + * \return a concatenation of the 63 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61, + const std::wstring& part62 +); + +/** + * Concatenate 64 strings. + * + * \param part0 1st part of the concatenation + * \param part1 2nd part of the concatenation + * \param part2 3rd part of the concatenation + * \param part3 4th part of the concatenation + * \param part4 5th part of the concatenation + * \param part5 6th part of the concatenation + * \param part6 7th part of the concatenation + * \param part7 8th part of the concatenation + * \param part8 9th part of the concatenation + * \param part9 10th part of the concatenation + * \param part10 11st part of the concatenation + * \param part11 12nd part of the concatenation + * \param part12 13rd part of the concatenation + * \param part13 14th part of the concatenation + * \param part14 15th part of the concatenation + * \param part15 16th part of the concatenation + * \param part16 17th part of the concatenation + * \param part17 18th part of the concatenation + * \param part18 19th part of the concatenation + * \param part19 20th part of the concatenation + * \param part20 21st part of the concatenation + * \param part21 22nd part of the concatenation + * \param part22 23rd part of the concatenation + * \param part23 24th part of the concatenation + * \param part24 25th part of the concatenation + * \param part25 26th part of the concatenation + * \param part26 27th part of the concatenation + * \param part27 28th part of the concatenation + * \param part28 29th part of the concatenation + * \param part29 30th part of the concatenation + * \param part30 31st part of the concatenation + * \param part31 32nd part of the concatenation + * \param part32 33rd part of the concatenation + * \param part33 34th part of the concatenation + * \param part34 35th part of the concatenation + * \param part35 36th part of the concatenation + * \param part36 37th part of the concatenation + * \param part37 38th part of the concatenation + * \param part38 39th part of the concatenation + * \param part39 40th part of the concatenation + * \param part40 41st part of the concatenation + * \param part41 42nd part of the concatenation + * \param part42 43rd part of the concatenation + * \param part43 44th part of the concatenation + * \param part44 45th part of the concatenation + * \param part45 46th part of the concatenation + * \param part46 47th part of the concatenation + * \param part47 48th part of the concatenation + * \param part48 49th part of the concatenation + * \param part49 50th part of the concatenation + * \param part50 51st part of the concatenation + * \param part51 52nd part of the concatenation + * \param part52 53rd part of the concatenation + * \param part53 54th part of the concatenation + * \param part54 55th part of the concatenation + * \param part55 56th part of the concatenation + * \param part56 57th part of the concatenation + * \param part57 58th part of the concatenation + * \param part58 59th part of the concatenation + * \param part59 60th part of the concatenation + * \param part60 61st part of the concatenation + * \param part61 62nd part of the concatenation + * \param part62 63rd part of the concatenation + * \param part63 64th part of the concatenation + * \return a concatenation of the 64 parts + */ +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61, + const std::wstring& part62, + const std::wstring& part63 +); + +/** + * Check if all the elements satisfy the \p condition. + * + * \param condition returning a boolean to be checked for each element + * \param container to be iterated through + * \return `true` if all the elements of \p container satisfy the \p condition + */ +template +bool All( + FunctorT condition, + const ContainerT& container +) { + for (const auto& item : container ) { + if (!condition(item)) { + return false; + } + } + return true; +} + +/** + * Check if any of the elements satisfy the \p condition. + * + * \param condition returning a boolean to be checked for each element + * \param container to be iterated through + * \return `true` if any of the elements of \p container satisfy the \p condition + */ +template +bool Some( + FunctorT condition, + const ContainerT& container +) { + for (const auto& item : container ) { + if (condition(item)) { + return true; + } + } + return false; +} + +/** + * Check if all the numbers in the range `[start, end)` satisfy the \p condition. + * + * The `IntegerT` is either `size_t` for the lengths, or `int64_t` for + * the integers, and needs to be given explicitly. + * + * \param condition returning a boolean to be checked for each number + * \param start of the range + * \param end of the range + * \return \parblock + * `true` if all the numbers between \p start and \p end (excluded) + * satisfy the \p condition + * \endparblock + */ +template +bool AllRange( + FunctorT condition, + IntegerT start, + IntegerT end +) { + for (IntegerT i = start; i < end; ++i) { + if (!condition(i)) { + return false; + } + } + return true; +} + +/** + * Check if any number in the range `[start, end)` satisfy the \p condition. + * + * The `IntegerT` is either `size_t` for the lengths, or `int64_t` for + * the integers, and needs to be given explicitly. + * + * \param condition returning a boolean to be checked for each number + * \param start of the range + * \param end of the range + * \return \parblock + * `true` if any number between \p start and + * \p end (excluded) satisfy the \p condition + * \endparblock + */ +template +bool SomeRange( + FunctorT condition, + IntegerT start, + IntegerT end +) { + for (IntegerT i = start; i < end; ++i) { + if (condition(i)) { + return true; + } + } + return false; +} + +/** + * Check if the \p container contains the \p value. + * + * \param container to be searched through + * \param value to be search for + * \return `true` if \p value is in the \p container + */ +template +bool Contains( + const ContainerT& container, + const ValueT& value +) { + const auto container_begin = std::begin(container); + const auto container_end = std::end(container); + return std::find( + container_begin, + container_end, + value + ) != container_end; +} + +/** + * Convert platform-independent the wide string to a UTF-8 string. + * + * \param text to be converted + * \return UTF-8 encoded \p text + */ +std::string WstringToUtf8(const std::wstring& text); + +/** + * Convert platform-independent the UTF-8 encoded string to a wide string. + * + * \param utf8_text UTF-8 encoded text to be converted + * \param utf8_text_size size of the text to be converted. If std::string::npos, + * the \p utf8_text is assumed to be null-terminated and the size is determined + * using `strlen`. + * \return the wide-string representation + */ +std::wstring Utf8ToWstring( + const char* utf8_text, + size_t utf8_text_size = std::string::npos +); + +/** + * Convert platform-independent the UTF-8 encoded string to a wide string. + * + * \param utf8_text UTF-8 encoded text to be converted + * \return wide string + */ +std::wstring Utf8ToWstring(const std::string& utf8_text); + +/** + * Count the items of a tuple. + * + * \return the number of items + */ +template +size_t LenTuple(const std::tuple&) { + return sizeof...(T); +} + +/** + * Count the characters (code points) of \p text. + * + * We follow the Python implementation of `len`, since Python is the language of + * the meta-model specifications. Hence, a character beyond the Basic + * Multilingual Plane counts as one, unlike in `text.size()` on the platforms + * where `wchar_t` is a UTF-16 code unit, such as Windows. + * + * \param text to be measured + * \return the number of characters + */ +size_t LenStr(const std::wstring& text); + +/** + * Slice \p text from \p start up to \p end, exclusive. + * + * We follow the Python implementation of slicing, since Python is the language + * of the meta-model specifications. Hence, the positions count the characters + * (code points), a negative position counts from the end, the positions out of + * range are clamped to the string, and the slice is empty if \p start is not + * before \p end. + * + * \param text to be sliced + * \param start of the slice, inclusive + * \param end of the slice, exclusive + * \return the slice + */ +std::wstring SliceStr( + const std::wstring& text, + int64_t start, + int64_t end +); + +/** + * Slice \p text from \p start up to its end. + * + * See the other overload for the semantics. + * + * \param text to be sliced + * \param start of the slice, inclusive + * \return the slice + */ +std::wstring SliceStr( + const std::wstring& text, + int64_t start +); + +/** + * Find the first \p sub in \p text. + * + * See the other overload for the semantics. + * + * \param text to be searched in + * \param sub to be searched for + * \return the position of \p sub in \p text, or -1 if not found + */ +int64_t FindStr( + const std::wstring& text, + const std::wstring& sub +); + +/** + * Find the first \p sub in \p text from \p start on. + * + * We follow the Python implementation of `str.find`, since Python is + * the language of the meta-model specifications. Hence, the positions count + * the characters (code points), a negative \p start counts from the end, and + * a \p start beyond the end of \p text gives -1. + * + * \param text to be searched in + * \param sub to be searched for + * \param start of the search + * \return the position of \p sub in \p text, or -1 if not found + */ +int64_t FindStr( + const std::wstring& text, + const std::wstring& sub, + int64_t start +); + +} // namespace common +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/constants.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/constants.hpp new file mode 100644 index 000000000..bce8482fe --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/constants.hpp @@ -0,0 +1,38 @@ +#ifndef DUMMY_CONSTANTS_GUARD_ +#define DUMMY_CONSTANTS_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup constants Pre-defined constants of the meta-model + * @{ + */ +namespace constants { + +/** + * Hash a blob of bytes based on the Java's String hash. + */ +struct HashBytes { + std::size_t operator()(const std::vector& bytes) const; +}; + +} // namespace common +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_CONSTANTS_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/enhancing.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/enhancing.hpp new file mode 100644 index 000000000..26a3a44ff --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/enhancing.hpp @@ -0,0 +1,1104 @@ +#ifndef DUMMY_ENHANCING_GUARD_ +#define DUMMY_ENHANCING_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/stringification.hpp" +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup enhancing Enhance instances of the model with your custom enhancements. + * @{ + */ +namespace enhancing { + +// region Forward declarations + +template +std::shared_ptr< + types::IItem +> Wrap( + const std::shared_ptr< + types::IItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +); + +template +std::shared_ptr< + types::ISomeItem +> Wrap( + const std::shared_ptr< + types::ISomeItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +); + +template +std::shared_ptr< + types::ISpecialItem +> Wrap( + const std::shared_ptr< + types::ISpecialItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +); + +template +std::shared_ptr< + types::IContainer +> Wrap( + const std::shared_ptr< + types::IContainer + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +); + +template +std::shared_ptr< + types::IWrapper +> Wrap( + const std::shared_ptr< + types::IWrapper + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +); + +// endregion Forward declarations + +/// \cond HIDDEN +namespace impl { + +template +class IEnhanced { + public: + virtual const std::shared_ptr& enhancement() const = 0; + + virtual std::shared_ptr& mutable_enhancement() = 0; + + virtual void set_enhancement( + std::shared_ptr value + ) = 0; + + virtual ~IEnhanced() = default; +}; + +template +class EnhancedSomeItem + : virtual public types::ISomeItem, + virtual public IEnhanced { + public: + types::ModelType model_type() const override { + return types::ModelType::kSomeItem; + } + + const std::wstring& name() const override { + return instance_->name(); + } + + std::wstring& mutable_name() override { + return instance_->mutable_name(); + } + + void set_name( + std::wstring value + ) override { + instance_->set_name(value); + } + + const common::optional& nickname() const override { + return instance_->nickname(); + } + + common::optional& mutable_nickname() override { + return instance_->mutable_nickname(); + } + + void set_nickname( + common::optional value + ) override { + instance_->set_nickname(value); + } + + const std::shared_ptr& enhancement() const { + return enhancement_; + } + + std::shared_ptr& mutable_enhancement() { + return enhancement_; + } + + void set_enhancement( + std::shared_ptr value + ) { + enhancement_ = std::move(value); + } + + EnhancedSomeItem( + std::shared_ptr instance, + std::shared_ptr enhancement + ) : + instance_(instance), + enhancement_(enhancement) { + // Intentionally empty. + } + + virtual ~EnhancedSomeItem() = default; + + private: + std::shared_ptr instance_; + std::shared_ptr enhancement_; +}; + +template +class EnhancedSpecialItem + : virtual public types::ISpecialItem, + virtual public IEnhanced { + public: + types::ModelType model_type() const override { + return types::ModelType::kSpecialItem; + } + + const std::wstring& name() const override { + return instance_->name(); + } + + std::wstring& mutable_name() override { + return instance_->mutable_name(); + } + + void set_name( + std::wstring value + ) override { + instance_->set_name(value); + } + + const common::optional& nickname() const override { + return instance_->nickname(); + } + + common::optional& mutable_nickname() override { + return instance_->mutable_nickname(); + } + + void set_nickname( + common::optional value + ) override { + instance_->set_nickname(value); + } + + int64_t level() const override { + return instance_->level(); + } + + int64_t& mutable_level() override { + return instance_->mutable_level(); + } + + void set_level( + int64_t value + ) override { + instance_->set_level(value); + } + + const std::shared_ptr& enhancement() const { + return enhancement_; + } + + std::shared_ptr& mutable_enhancement() { + return enhancement_; + } + + void set_enhancement( + std::shared_ptr value + ) { + enhancement_ = std::move(value); + } + + EnhancedSpecialItem( + std::shared_ptr instance, + std::shared_ptr enhancement + ) : + instance_(instance), + enhancement_(enhancement) { + // Intentionally empty. + } + + virtual ~EnhancedSpecialItem() = default; + + private: + std::shared_ptr instance_; + std::shared_ptr enhancement_; +}; + +template +class EnhancedContainer + : virtual public types::IContainer, + virtual public IEnhanced { + public: + types::ModelType model_type() const override { + return types::ModelType::kContainer; + } + + const std::vector< + std::shared_ptr + >& items() const override { + return instance_->items(); + } + + std::vector< + std::shared_ptr + >& mutable_items() override { + return instance_->mutable_items(); + } + + void set_items( + std::vector< + std::shared_ptr + > value + ) override { + instance_->set_items(value); + } + + const std::vector< + std::shared_ptr + >& some_items() const override { + return instance_->some_items(); + } + + std::vector< + std::shared_ptr + >& mutable_some_items() override { + return instance_->mutable_some_items(); + } + + void set_some_items( + std::vector< + std::shared_ptr + > value + ) override { + instance_->set_some_items(value); + } + + const common::optional< + std::vector< + std::shared_ptr + > + >& special_items() const override { + return instance_->special_items(); + } + + common::optional< + std::vector< + std::shared_ptr + > + >& mutable_special_items() override { + return instance_->mutable_special_items(); + } + + void set_special_items( + common::optional< + std::vector< + std::shared_ptr + > + > value + ) override { + instance_->set_special_items(value); + } + + int64_t CountItems( + const std::vector< + std::shared_ptr + >& items + ) const override { + return instance_->CountItems( + items + ); + } + + const std::shared_ptr& enhancement() const { + return enhancement_; + } + + std::shared_ptr& mutable_enhancement() { + return enhancement_; + } + + void set_enhancement( + std::shared_ptr value + ) { + enhancement_ = std::move(value); + } + + EnhancedContainer( + std::shared_ptr instance, + std::shared_ptr enhancement + ) : + instance_(instance), + enhancement_(enhancement) { + // Intentionally empty. + } + + virtual ~EnhancedContainer() = default; + + private: + std::shared_ptr instance_; + std::shared_ptr enhancement_; +}; + +template +class EnhancedWrapper + : virtual public types::IWrapper, + virtual public IEnhanced { + public: + types::ModelType model_type() const override { + return types::ModelType::kWrapper; + } + + const std::shared_ptr& container() const override { + return instance_->container(); + } + + std::shared_ptr& mutable_container() override { + return instance_->mutable_container(); + } + + void set_container( + std::shared_ptr value + ) override { + instance_->set_container(value); + } + + const std::shared_ptr& enhancement() const { + return enhancement_; + } + + std::shared_ptr& mutable_enhancement() { + return enhancement_; + } + + void set_enhancement( + std::shared_ptr value + ) { + enhancement_ = std::move(value); + } + + EnhancedWrapper( + std::shared_ptr instance, + std::shared_ptr enhancement + ) : + instance_(instance), + enhancement_(enhancement) { + // Intentionally empty. + } + + virtual ~EnhancedWrapper() = default; + + private: + std::shared_ptr instance_; + std::shared_ptr enhancement_; +}; + +/** + * Wrap recursively the instances held by \p that. + * + * \param that container of the instances to be wrapped + * \param factory to produce an enhancement based on an instance + * \return a copy of \p that with the wrapped instances + * + * \tparam E type of the enhancement + */ +template +std::vector< + std::shared_ptr +> Wrap_listOf_Item( + const std::vector< + std::shared_ptr + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + std::vector< + std::shared_ptr + > result; + result.reserve(that.size()); + + for ( + const std::shared_ptr& item : + that + ) { + result.emplace_back(Wrap(item, factory)); + } + + return result; +} + +/** + * Wrap recursively the instances held by \p that. + * + * \param that container of the instances to be wrapped + * \param factory to produce an enhancement based on an instance + * \return a copy of \p that with the wrapped instances + * + * \tparam E type of the enhancement + */ +template +std::vector< + std::shared_ptr +> Wrap_listOf_SomeItem( + const std::vector< + std::shared_ptr + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + std::vector< + std::shared_ptr + > result; + result.reserve(that.size()); + + for ( + const std::shared_ptr& item : + that + ) { + result.emplace_back(Wrap(item, factory)); + } + + return result; +} + +/** + * Wrap recursively the instances held by \p that. + * + * \param that container of the instances to be wrapped + * \param factory to produce an enhancement based on an instance + * \return a copy of \p that with the wrapped instances + * + * \tparam E type of the enhancement + */ +template +std::vector< + std::shared_ptr +> Wrap_listOf_SpecialItem( + const std::vector< + std::shared_ptr + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + std::vector< + std::shared_ptr + > result; + result.reserve(that.size()); + + for ( + const std::shared_ptr& item : + that + ) { + result.emplace_back(Wrap(item, factory)); + } + + return result; +} + +/** + * Wrap \p that with an enhanced instance. + * + * \param that instance to be wrapped and enhanced + * \param factory to produce an enhancement based on an instance + * \return Enhanced instance, or `that` if no enhancement produced + * + * \tparam E type of the enhancement + */ +template +std::shared_ptr WrapSomeItem( + const std::shared_ptr& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + // No properties to be recursively enhanced. + + std::shared_ptr enh( + factory(that) + ); + return (enh == nullptr) + ? that + : std::shared_ptr( + new EnhancedSomeItem( + that, + enh + ) + ); +} + +/** + * Wrap \p that with an enhanced instance. + * + * \param that instance to be wrapped and enhanced + * \param factory to produce an enhancement based on an instance + * \return Enhanced instance, or `that` if no enhancement produced + * + * \tparam E type of the enhancement + */ +template +std::shared_ptr WrapSpecialItem( + const std::shared_ptr& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + // No properties to be recursively enhanced. + + std::shared_ptr enh( + factory(that) + ); + return (enh == nullptr) + ? that + : std::shared_ptr( + new EnhancedSpecialItem( + that, + enh + ) + ); +} + +/** + * Wrap \p that with an enhanced instance. + * + * \param that instance to be wrapped and enhanced + * \param factory to produce an enhancement based on an instance + * \return Enhanced instance, or `that` if no enhancement produced + * + * \tparam E type of the enhancement + */ +template +std::shared_ptr WrapContainer( + const std::shared_ptr& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + that->set_items( + Wrap_listOf_Item(that->items(), factory) + ); + + that->set_some_items( + Wrap_listOf_SomeItem(that->some_items(), factory) + ); + + if (that->special_items().has_value()) { + that->set_special_items( + common::make_optional( + Wrap_listOf_SpecialItem( + that->special_items().value(), + factory + ) + ) + ); + } + + std::shared_ptr enh( + factory(that) + ); + return (enh == nullptr) + ? that + : std::shared_ptr( + new EnhancedContainer( + that, + enh + ) + ); +} + +/** + * Wrap \p that with an enhanced instance. + * + * \param that instance to be wrapped and enhanced + * \param factory to produce an enhancement based on an instance + * \return Enhanced instance, or `that` if no enhancement produced + * + * \tparam E type of the enhancement + */ +template +std::shared_ptr WrapWrapper( + const std::shared_ptr& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + that->set_container( + Wrap(that->container(), factory) + ); + + std::shared_ptr enh( + factory(that) + ); + return (enh == nullptr) + ? that + : std::shared_ptr( + new EnhancedWrapper( + that, + enh + ) + ); +} + +/** + * Assert that the \p that instance has not been already enhanced. + * + * \param that instance to be checked + * \tparam E type of the enhancement + * \tparam T interface type of \p that instance + * \throw std::logic_error if \p that already enhanced + */ +template< + typename E, + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +void AssertNotEnhanced( + const std::shared_ptr& that +) { + std::shared_ptr > enhanced( + std::dynamic_pointer_cast< + impl::IEnhanced + >(that) + ); + if (enhanced != nullptr) { + throw std::logic_error( + common::Concat( + "An instance of ", + stringification::to_string(that->model_type()), + " has been already wrapped." + ) + ); + } +} + +} // namespace impl +/// \endcond + +/** + * Wrap \p that instance recursively with the enhancement produced by the \p factory. + * + * The factory decides itself whether it will produce an enhancement for + * \p that instance, or not. Even if no enhancement has been produced for \p that + * instance, we will still continue to enhance the instances referenced + * by \p that instance recursively. + * + * \param that instance to wrap + * \param factory to selectively produce an enhancement + * \return enhanced \p that instance + * \throw std::logic_error if \p that instance has been already wrapped. + * \tparam E type of the enhancement + */ +///@{ + +template +std::shared_ptr< + types::IClass +> Wrap( + const std::shared_ptr< + types::IClass + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::IClass + >(that); + + switch (that->model_type()) { + case types::ModelType::kSomeItem: + return impl::WrapSomeItem( + std::dynamic_pointer_cast< + types::ISomeItem + >(that), + factory + ); + break; + case types::ModelType::kSpecialItem: + return impl::WrapSpecialItem( + std::dynamic_pointer_cast< + types::ISpecialItem + >(that), + factory + ); + break; + case types::ModelType::kContainer: + return impl::WrapContainer( + std::dynamic_pointer_cast< + types::IContainer + >(that), + factory + ); + break; + case types::ModelType::kWrapper: + return impl::WrapWrapper( + std::dynamic_pointer_cast< + types::IWrapper + >(that), + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +template +std::shared_ptr< + types::IItem +> Wrap( + const std::shared_ptr< + types::IItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::IItem + >(that); + + switch (that->model_type()) { + case types::ModelType::kSomeItem: + return impl::WrapSomeItem( + std::dynamic_pointer_cast< + types::ISomeItem + >(that), + factory + ); + break; + case types::ModelType::kSpecialItem: + return impl::WrapSpecialItem( + std::dynamic_pointer_cast< + types::ISpecialItem + >(that), + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +template +std::shared_ptr< + types::ISomeItem +> Wrap( + const std::shared_ptr< + types::ISomeItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::ISomeItem + >(that); + + switch (that->model_type()) { + case types::ModelType::kSomeItem: + return impl::WrapSomeItem( + that, + factory + ); + break; + case types::ModelType::kSpecialItem: + return impl::WrapSpecialItem( + std::dynamic_pointer_cast< + types::ISpecialItem + >(that), + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +template +std::shared_ptr< + types::ISpecialItem +> Wrap( + const std::shared_ptr< + types::ISpecialItem + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::ISpecialItem + >(that); + + switch (that->model_type()) { + case types::ModelType::kSpecialItem: + return impl::WrapSpecialItem( + that, + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +template +std::shared_ptr< + types::IContainer +> Wrap( + const std::shared_ptr< + types::IContainer + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::IContainer + >(that); + + switch (that->model_type()) { + case types::ModelType::kContainer: + return impl::WrapContainer( + that, + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +template +std::shared_ptr< + types::IWrapper +> Wrap( + const std::shared_ptr< + types::IWrapper + >& that, + const std::function< + std::shared_ptr( + const std::shared_ptr& + ) + >& factory +) { + impl::AssertNotEnhanced< + E, + types::IWrapper + >(that); + + switch (that->model_type()) { + case types::ModelType::kWrapper: + return impl::WrapWrapper( + that, + factory + ); + break; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that->model_type() + ) + ) + ) + ); + break; + } +} + +///@}} + +/** + * Try to unwrap the enhancement from \p that instance. + * + * \param that instance possibly wrapped with an enhancement + * \return the enhancement, or `nullptr` if \p that instance has not been wrapped + * \tparam E type of the enhancement + */ +template +std::shared_ptr Unwrap( + const std::shared_ptr& that +) { + const std::shared_ptr >& maybe_enhanced( + std::dynamic_pointer_cast >(that) + ); + + if (!maybe_enhanced) { + return nullptr; + } + + return maybe_enhanced->enhancement(); +} + +/** + * Unwrap the enhancement from \p that instance. + * + * \remark \p that instance must have been wrapped before. + * + * \param that instance expected to be wrapped with an enhancement + * \return the enhancement + * \throw std::invalid_argument if \p that instance has not been wrapped + * \tparam E type of the enhancement + */ +template +std::shared_ptr MustUnwrap( + const std::shared_ptr& that +) { + std::shared_ptr enhancement( + Unwrap(that) + ); + if (!enhancement) { + throw std::invalid_argument( + common::Concat( + "Expected an instance of ", + stringification::to_string(that->model_type()), + " to have been already wrapped with an enhancement, " + "but it has been not." + ) + ); + } + return enhancement; +} + +} // namespace enhancing +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_ENHANCING_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/iteration.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/iteration.hpp new file mode 100644 index 000000000..181d9c1c1 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/iteration.hpp @@ -0,0 +1,372 @@ +#ifndef DUMMY_ITERATION_GUARD_ +#define DUMMY_ITERATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup iteration Define functions and structures to iterate over instances. + * @{ +*/ +namespace iteration { + +// region Pathing + +/** + * Define the properties over all the classes to compactly represent the paths. + */ +enum class Property : std::uint32_t { + kContainer = 0, + kItems = 1, + kLevel = 2, + kName = 3, + kNickname = 4, + kSomeItems = 5, + kSpecialItems = 6 +}; + +std::wstring PropertyToWstring( + Property property +); + +/** + * Represent a segment of a path to some value. + */ +class ISegment { + public: + virtual std::wstring ToWstring() const = 0; + virtual std::unique_ptr Clone() const = 0; + virtual ~ISegment() = default; +}; // class ISegment + +/** + * Represent a property access on a path. + */ +struct PropertySegment : public ISegment { + /** + * Enumeration of the property + */ + Property property; + + PropertySegment( + Property a_property + ); + + std::wstring ToWstring() const override; + std::unique_ptr Clone() const override; + + ~PropertySegment() override = default; +}; // struct PropertySegment + +/** + * Represent an index access on a path. + */ +struct IndexSegment : public ISegment { + /** + * Index of the item + */ + size_t index; + + explicit IndexSegment( + size_t an_index + ); + + std::wstring ToWstring() const override; + std::unique_ptr Clone() const override; + + ~IndexSegment() override = default; +}; // struct IndexSegment + +/** + * \brief Represent an access to a key of a JSON-able object on a path. + * + * Unlike \ref PropertySegment, which points to one of the properties of + * the meta-model, this segment points to a key of a JSON-able object, which + * can be an arbitrary string known only at runtime. + */ +struct KeySegment : public ISegment { + /** + * Key of the JSON-able object + */ + std::wstring key; + + explicit KeySegment( + std::wstring a_key + ); + + std::wstring ToWstring() const override; + std::unique_ptr Clone() const override; + + ~KeySegment() override = default; +}; // struct KeySegment + +/** + * \brief Represent a path to some value. + * + * This is a path akin to C++ expressions. It is not to be confused with different + * paths used in the specification. This path class is meant to help with reporting. + * For example, we can use this path to let the user know when there is + * a verification error in a model which can concern instances, but also properties + * and items in the lists. + */ +struct Path { + // NOTE (mristin): + // We did not implement the reflection at the moment since we did not have a use + // case for it. If you need reflection, please contact the developers. It should + // be a small step going from paths to dereferencing to getters and setters. + + std::deque > segments; + + Path(); + Path(const Path& other); + Path(Path&& other); + Path& operator=(const Path& other); + Path& operator=(Path&& other); + + std::wstring ToWstring() const; +}; // struct Path + +// endregion Pathing + +// region Iterators and descent + +/// \cond HIDDEN +namespace impl { +class IIterator { + public: + virtual void Start() = 0; + virtual void Next() = 0; + virtual bool Done() const = 0; + virtual const std::shared_ptr& Get() const = 0; + + /** + * \brief Append the segments leading to the current instance to the \p path. + * + * Only called on request, so the iteration itself builds no paths. + */ + virtual void AppendToPath(Path& path) const = 0; + + virtual std::unique_ptr Clone() const = 0; + + virtual ~IIterator() = default; +}; // class IIterator +} // namespace impl +/// \endcond + +/** + * \brief Iterate over a model instance. + * + * Unlike STL, this is not a light-weight iterator. We implement + * a "yielding" iterator by composing iterators over the properties so that we + * always keep the model stack as well as the properties iterated thus far. + * + * This means that copy-construction and equality comparisons are much more heavy-weight + * than you'd usually expect from an STL iterator. For example, if you want to sort + * model instances, you are most probably faster if you populate a vector, and then + * sort the vector. + * + * Also, given that this iterator is not light-weight, you should in almost all cases + * avoid the postfix increment (it++) and prefer the prefix one (++it) as the postfix + * increment would create an iterator copy every time. + * + * The value of the iterator is intentionally constant reference to a shared pointer. + * This merely means that you can not change the pointer while you are + * iterating. The pointed instances, however, is freely mutable. This way you can make + * further shared pointers, or include the pointed instances in other collections + * different from the original container. On the other hand, the normal case, where + * the pointer is only de-referenced, remains efficient as no copy of + * the shared pointer is created. + * + * We follow the C++ standard, and assume that comparison between the two iterators + * over two different instances results in undefined behavior. See + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2948.html and + * https://stackoverflow.com/questions/4657513/comparing-iterators-from-different-containers. + * + * Since we use const references to shared pointers here you can also share ownership + * over instances in your own external containers. Making a copy of a shared pointer + * will automatically increase reference count, even though there is a constant + * reference. Since we do not make copies of the shared pointers, it is very important + * that the given shared pointers outlive the iteration, lest cause undefined behavior. + * + * Changing the references during the iteration invalidates the iterators and + * results in undefined behavior. This is similar to many of the containers in the STL, + * see: https://stackoverflow.com/questions/6438086/iterator-invalidation-rules-for-c-containers + * + * See these StackOverflow questions for performance related to shared pointers and + * constant references to shared pointers (copying versus referencing): + * * https://stackoverflow.com/questions/12002480/passing-stdshared-ptr-to-constructors/12002668#12002668 + * * https://stackoverflow.com/questions/3310737/should-we-pass-a-shared-ptr-by-reference-or-by-value + * * https://stackoverflow.com/questions/37610494/passing-const-shared-ptrt-versus-just-shared-ptrt-as-parameter + * + * The following StackOverflow question and answers go into more detail how const-ness + * and shared pointers fit together: + * https://stackoverflow.com/questions/36271663/why-does-copying-a-const-shared-ptr-not-violate-const-ness + */ +class Iterator { + using iterator_category = std::forward_iterator_tag; + /// The difference is meaningless, but has to be defined. + using difference_type = std::ptrdiff_t; + using value_type = std::shared_ptr; + using pointer = const std::shared_ptr*; + using reference = const std::shared_ptr&; + + public: + Iterator(const Iterator& other); + Iterator(Iterator&& other); + + Iterator& operator=(const Iterator& other); + Iterator& operator=(Iterator&& other); + + reference operator*() const; + pointer operator->(); + + // Prefix increment + Iterator& operator++(); + + // Postfix increment + Iterator operator++(int); + + friend bool operator==(const Iterator& a, const Iterator& b); + friend bool operator!=(const Iterator& a, const Iterator& b); + + friend class Descent; + friend class DescentOnce; + friend Path MaterializePath(const Iterator& iterator); + friend void PrependToPath(const Iterator& iterator, Path* path); + + private: + explicit Iterator( + std::unique_ptr implementation + ) : + implementation_(std::move(implementation)), + index_(implementation_->Done() ? -1 : 0) { + // Intentionally empty. + } + + std::unique_ptr implementation_; + + /** + * Count the instances iterated thus far so that we can compare the iterators, + * or -1 if the iteration is done. + */ + long index_; +}; + +bool operator==(const Iterator& a, const Iterator& b); + +bool operator!=(const Iterator& a, const Iterator& b); + +/** + * \brief Materialize the path that the \p iterator points to. + * + * We assume that you always want a copy of the path, rather than inspect + * the path during the iteration. + * + * \param iterator for which we want to materialize the path + * \return Path referring to the pointed instance + */ +Path MaterializePath(const Iterator& iterator); + +/** + * Build a facade over an instance to iterate over instances referenced from it. + */ +class IDescent { + public: + virtual Iterator begin() const = 0; + virtual const Iterator& end() const = 0; + virtual ~IDescent() = default; +}; // class IDescent + +/** + * \brief Provide a recursive iterable over all the instances referenced from + * an instance. + * + * Please see the notes in the class Iterator regarding the constant reference to + * a shared pointer. In short, the instance should outlive the descent, so make + * sure you do not destroy it during the descent. + * + * Range-based loops should fit the vast majority of the use cases: + * \code + * std::shared_ptr env = ...; + * for ( + * const std::shared_ptr& instance + * : Descent(env) + * ) { + * do_something(instance); + * } + * \endcode + * + * \param that instance to be iterated over recursively + * \return Iterable over referenced instances + */ +class Descent : public IDescent { + public: + Descent( + std::shared_ptr instance + ); + + Iterator begin() const override; + const Iterator& end() const override; + + ~Descent() override = default; + + private: + std::shared_ptr instance_; +}; // class Descent + +/** + * \brief Provide a non-recursive iterable over the instances referenced from + * an instance. + * + * Please see the notes in the class Iterator regarding the constant reference to + * a shared pointer. In short, the instance should outlive the descent, so make + * sure you do not destroy it during the descent. + * + * Range-based loops should fit the vast majority of the use cases: + * \code + * std::shared_ptr env = ...; + * for ( + * const std::shared_ptr& instance + * : DescentOnce(env) + * ) { + * do_something(instance); + * } + * \endcode + */ +class DescentOnce : public IDescent { + public: + DescentOnce( + std::shared_ptr instance + ); + + Iterator begin() const override; + const Iterator& end() const override; + + ~DescentOnce() override = default; + + private: + std::shared_ptr instance_; +}; // class DescentOnce + +// endregion Iterators and descent + +} // namespace iteration +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_ITERATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/jsonization.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/jsonization.hpp new file mode 100644 index 000000000..92d14a07c --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/jsonization.hpp @@ -0,0 +1,272 @@ +#ifndef DUMMY_JSONIZATION_GUARD_ +#define DUMMY_JSONIZATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/iteration.hpp" +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include + +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup jsonization De/serialize instances from and to JSON. + * @{ + */ +namespace jsonization { + +/** + * Represent a segment of a JSON path to some value. + */ +class ISegment { + public: + /** + * \brief Convert the segment to a string in a JSON path. + */ + virtual std::wstring ToWstring() const = 0; + + virtual std::unique_ptr Clone() const = 0; + + virtual ~ISegment() = default; +}; // class ISegment + +/** + * Represent a property access on a JSON path. + */ +struct PropertySegment : public ISegment { + /** + * Name of the property in a JSON object + */ + std::wstring name; + + PropertySegment( + std::wstring a_name + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~PropertySegment() override = default; +}; // struct PropertySegment + +/** + * Represent an index access on a JSON path. + */ +struct IndexSegment : public ISegment { + /** + * Index of the value in an array. + */ + size_t index; + + explicit IndexSegment( + size_t an_index + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~IndexSegment() override = default; +}; // struct IndexSegment + +/** + * Represent an access to a member of a JSON object whose key is known only at + * run time, such as a member of a JSON-able object, on a JSON path. + */ +struct KeySegment : public ISegment { + /** + * Key of the member in a JSON object + */ + std::wstring key; + + explicit KeySegment( + std::wstring a_key + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~KeySegment() override = default; +}; // struct KeySegment + +/** + * Represent a JSON path to some value. + */ +struct Path { + std::deque > segments; + + Path(); + Path(const Path& other); + Path(Path&& other); + Path& operator=(const Path& other); + Path& operator=(Path&& other); + + std::wstring ToWstring() const; +}; // struct Path + +// region De-serialization + +/** + * Represent a de-serialization error. + */ +struct DeserializationError { + /** + * Human-readable description of the error + */ + std::wstring cause; + + /** + * Path to the erroneous value + */ + Path path; + + explicit DeserializationError(std::wstring a_cause); + DeserializationError(std::wstring a_cause, Path a_path); +}; // struct DeserializationError + +/** + * \brief Deserialize \p json value to an instance + * of types::IItem. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return The deserialized instance, or a de-serialization error, if any. + */ +common::expected< + std::shared_ptr, + DeserializationError +> ItemFrom( + const nlohmann::json& json, + bool additional_properties = false +); + +/** + * \brief Deserialize \p json value to an instance + * of types::ISomeItem. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return The deserialized instance, or a de-serialization error, if any. + */ +common::expected< + std::shared_ptr, + DeserializationError +> SomeItemFrom( + const nlohmann::json& json, + bool additional_properties = false +); + +/** + * \brief Deserialize \p json value to an instance + * of types::ISpecialItem. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return The deserialized instance, or a de-serialization error, if any. + */ +common::expected< + std::shared_ptr, + DeserializationError +> SpecialItemFrom( + const nlohmann::json& json, + bool additional_properties = false +); + +/** + * \brief Deserialize \p json value to an instance + * of types::IContainer. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return The deserialized instance, or a de-serialization error, if any. + */ +common::expected< + std::shared_ptr, + DeserializationError +> ContainerFrom( + const nlohmann::json& json, + bool additional_properties = false +); + +/** + * \brief Deserialize \p json value to an instance + * of types::IWrapper. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return The deserialized instance, or a de-serialization error, if any. + */ +common::expected< + std::shared_ptr, + DeserializationError +> WrapperFrom( + const nlohmann::json& json, + bool additional_properties = false +); + +// endregion Deserialization + +// region Serialization + +/** + * Represent an error in the serialization of an instance to JSON. + */ +class SerializationException : public std::exception { + public: + SerializationException( + std::wstring cause, + iteration::Path path + ); + + const char* what() const noexcept override; + + const std::wstring& cause() const noexcept; + const iteration::Path& path() const noexcept; + + ~SerializationException() noexcept override = default; + + private: + const std::wstring cause_; + const iteration::Path path_; + const std::string msg_; +}; // class SerializationException + +/** + * \brief Serialize \p that instance to a JSON value. + * + * \param that instance to be serialized + * \return The corresponding JSON value + * \throw \ref SerializationException if a value within \p that instance + * could not be serialized + */ +nlohmann::json Serialize( + const types::IClass& that +); + +// endregion Serialization + +} // namespace jsonization +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_JSONIZATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/pattern.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/pattern.hpp new file mode 100644 index 000000000..5dc94a051 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/pattern.hpp @@ -0,0 +1,34 @@ +#ifndef DUMMY_PATTERN_GUARD_ +#define DUMMY_PATTERN_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/revm.hpp" + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup pattern Provide patterns to be matched using a multi-threaded virtual machine. + * + * The instructions should be supplied to dummy::revm::Match. While + * we could have theoretically included this code in verification, we decided to keep + * it separate for readability. You are not expected to use this module directly. + * @{ + */ +namespace pattern { + +} // namespace pattern +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_PATTERN_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/revm.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/revm.hpp new file mode 100644 index 000000000..2d7dec68f --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/revm.hpp @@ -0,0 +1,213 @@ +#ifndef DUMMY_REVM_GUARD_ +#define DUMMY_REVM_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#pragma warning(push, 0) +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup revm Match regular expressions using a multi-threaded virtual machine. + * + * The implementation in the standard library has exponential time complexity, so it was + * a major blocker for most of the practical inputs. For example, see this bug report: + * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93502 + * + * This implementation is based on Ken Thompson's approach which uses a virtual + * machine to match regular expressions. The original technique has been + * published in: + * Thompson, K., "Regular expression search algorithm", ACM 11(6) (June 1968) + * + * We followed a very clear and concise blog post which described in detail: + * https://swtch.com/~rsc/regexp/regexp2.html + * + * The ideas for additional instructions were taken from: + * https://www.codeproject.com/Articles/5256833/Regex-as-a-Tiny-Threaded-Virtual-Machine + * @{ + */ +namespace revm { + +enum class InstructionKind : std::uint8_t { + Char, + Set, + NotSet, + Any, + Match, + Jump, + Split, + End +}; + +/** + * Represent an instruction of the virtual machine. + */ +struct Instruction { + // NOTE (mristin): + // We avoid RTTI for performance reasons, and use our own enumerator instead. + virtual InstructionKind kind() const = 0; + + virtual ~Instruction() = default; +}; + +/** + * Match a single character. + * + * If the character on the String Pointer does not match the `character`, stop this + * thread as it failed. Otherwise, move the String Pointer to the next character, + * and the Program Counter to the next instruction. + */ +struct InstructionChar : Instruction { + wchar_t character; + + explicit InstructionChar(wchar_t a_character); + InstructionKind kind() const override; + ~InstructionChar() override = default; +}; + +std::string to_string(const InstructionChar& instruction); + +/** + * Define a character range. + */ +struct Range { + wchar_t first; + wchar_t last; + + Range(wchar_t a_first, wchar_t a_last); +}; + +std::string to_string(const Range& range); + +/** + * Check whether the character is in any of the given character ranges. + * + * @return true if the character is in any of the ranges + */ +bool CharacterInRanges( + const std::vector& ranges, + wchar_t character +); + +/** + * Match a set of characters. + * + * If the character on the String Pointer *is not* in the given set, stop this + * thread as it failed. Otherwise, move the String Pointer to the next character, + * and the Program Counter to the next instruction. + */ +struct InstructionSet : Instruction { + std::vector ranges; + + explicit InstructionSet(std::vector a_ranges); + InstructionKind kind() const override; + ~InstructionSet() override = default; +}; + +std::string to_string(const InstructionSet& instruction); + +/** + * Match an out-of-set character. + * + * If the character on the String Pointer *is* in the given set, stop this + * thread as it failed. Otherwise, move the String Pointer to the next character, + * and the Program Counter to the next instruction. + */ +struct InstructionNotSet : Instruction { + std::vector ranges; + + explicit InstructionNotSet(std::vector a_ranges); + InstructionKind kind() const override; + ~InstructionNotSet() override = default; +}; + +std::string to_string(const InstructionNotSet& instruction); + +/** + * Match any character. + */ +struct InstructionAny : Instruction { + InstructionKind kind() const override; + ~InstructionAny() override = default; +}; + +std::string to_string(const InstructionAny&); + +/** + * Stop this thread and signal that we found a match. + */ +struct InstructionMatch : Instruction { + InstructionKind kind() const override; + ~InstructionMatch() override = default; +}; + +std::string to_string(const InstructionMatch&); + +/** + * Jump to the indicated position in the program. + */ +struct InstructionJump : Instruction { + size_t target; + + explicit InstructionJump(size_t a_target); + InstructionKind kind() const override; + ~InstructionJump() override = default; +}; + +std::string to_string(const InstructionJump& instruction); + +/** + * Split the program in two threads, both jumping to different locations. The string + * pointer is kept as-is. + */ +struct InstructionSplit : Instruction { + size_t first_target; + size_t second_target; + + explicit InstructionSplit(size_t a_first_target, size_t a_second_target); + InstructionKind kind() const override; + ~InstructionSplit() override = default; +}; + +std::string to_string(const InstructionSplit& instruction); + +/** + * Match the end-of-input. + */ +struct InstructionEnd : Instruction { + InstructionKind kind() const override; + ~InstructionEnd() override = default; +}; + +std::string to_string(const InstructionEnd&); + +std::string to_string(const Instruction& instruction); + +std::string to_string( + const std::vector >& instructions +); + +/** + * Try to match the program against the text. + * @return true if the text matches + */ +bool Match( + const std::vector >& program, + const std::wstring& text +); + +} // namespace revm +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_REVM_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/stringification.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/stringification.hpp new file mode 100644 index 000000000..66dfea760 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/stringification.hpp @@ -0,0 +1,91 @@ +#ifndef DUMMY_STRINGIFICATION_GUARD_ +#define DUMMY_STRINGIFICATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup stringification Translate to strings, and, where applicable, from strings. + * @{ + */ +namespace stringification { + +/** + * Try to parse the \p text as a model type literal. + * + * \param text to be parsed + * \return literal, or nothing, if \p text invalid + */ +common::optional ModelTypeFromString( + const std::string& text +); + +/** + * Parse the \p text as a model type literal. + * + * \param text to be parsed + * \return literal + * \throw std::invalid_argument if \p text invalid + */ +types::ModelType MustModelTypeFromString( + const std::string& text +); + +/** + * Translate the enumeration literal \p model_type to text. + * + * \param model_type to be converted into text + * \return text representation of \p model_type + * \throw std::invalid_argument if \p model_type invalid + */ +std::string to_string( + types::ModelType model_type +); + +/** + * Encode the \p bytes with base64 to a std::string. + * + * \param bytes to be encoded + * \return base64-encoding of \p bytes + */ +std::string Base64Encode( + const std::vector& bytes +); + +/** + * Decode the \p the text with base64 to bytes. + * + * \remark \parblock + * We intentionally decode from std::string and *not* from std::wstring as + * the de/serialization libraries currently work only with UTF-8 encoded strings. + * \endparblock + * + * \param text to be decoded + * \return decoded bytes, or error message, if any. + */ +common::expected< + std::vector, + std::string +> Base64Decode( + const std::string& text +); + +} // namespace stringification +} // namespace dummy + +/**@}*/ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_STRINGIFICATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/types.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/types.hpp new file mode 100644 index 000000000..04a9a21c2 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/types.hpp @@ -0,0 +1,512 @@ +#ifndef DUMMY_TYPES_GUARD_ +#define DUMMY_TYPES_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup types Define data structures corresponding to the meta-model. + * @{ + */ +namespace types { + +// region Enumerations + +/** + * Enumerate the model types for faster type switches. + * + * For example, switch statements can be implemented as jump tables. + */ +enum class ModelType : std::uint32_t { + kSomeItem = 0, + kSpecialItem = 1, + kContainer = 2, + kWrapper = 3 +}; + +// endregion Enumerations + +// region Forward declaration of interfaces + +// endregion Forward declaration of interfaces + +class IItem; + +class ISomeItem; + +class ISpecialItem; + +class IContainer; + +class IWrapper; + +// region Class interfaces + +/** + * Model the most general instance of the model. + */ +class IClass { + public: + /** + * Indicate the runtime model type. + */ + virtual ModelType model_type() const = 0; + virtual ~IClass() = default; +}; + +class IItem + : virtual public IClass { + public: + virtual const std::wstring& name() const = 0; + + virtual std::wstring& mutable_name() = 0; + + virtual void set_name( + std::wstring value + ) = 0; + + virtual const common::optional& nickname() const = 0; + + virtual common::optional& mutable_nickname() = 0; + + virtual void set_nickname( + common::optional value + ) = 0; + + virtual ~IItem() = default; +}; + +class ISomeItem + : virtual public IItem { + public: + virtual ~ISomeItem() = default; +}; + +class ISpecialItem + : virtual public ISomeItem { + public: + virtual int64_t level() const = 0; + + virtual int64_t& mutable_level() = 0; + + virtual void set_level( + int64_t value + ) = 0; + + virtual ~ISpecialItem() = default; +}; + +class IContainer + : virtual public IClass { + public: + virtual const std::vector< + std::shared_ptr + >& items() const = 0; + + virtual std::vector< + std::shared_ptr + >& mutable_items() = 0; + + virtual void set_items( + std::vector< + std::shared_ptr + > value + ) = 0; + + virtual const std::vector< + std::shared_ptr + >& some_items() const = 0; + + virtual std::vector< + std::shared_ptr + >& mutable_some_items() = 0; + + virtual void set_some_items( + std::vector< + std::shared_ptr + > value + ) = 0; + + virtual const common::optional< + std::vector< + std::shared_ptr + > + >& special_items() const = 0; + + virtual common::optional< + std::vector< + std::shared_ptr + > + >& mutable_special_items() = 0; + + virtual void set_special_items( + common::optional< + std::vector< + std::shared_ptr + > + > value + ) = 0; + + /// \brief Count the \p items. + /// + /// The arguments of the methods are never generic, so only the sequences of + /// IItem can be passed to it, but not the sequences of + /// the descendants. + virtual int64_t CountItems( + const std::vector< + std::shared_ptr + >& items + ) const = 0; + + virtual ~IContainer() = default; +}; + +class IWrapper + : virtual public IClass { + public: + virtual const std::shared_ptr& container() const = 0; + + virtual std::shared_ptr& mutable_container() = 0; + + virtual void set_container( + std::shared_ptr value + ) = 0; + + virtual ~IWrapper() = default; +}; + +// endregion + +// region Definitions of concrete classes + +class SomeItem + : public ISomeItem { + public: + explicit SomeItem( + std::wstring name, + common::optional nickname = common::nullopt + ); + + ModelType model_type() const override; + + // region Get and set name_ + + const std::wstring& name() const override; + + std::wstring& mutable_name() override; + + void set_name( + std::wstring value + ) override; + + // endregion + + // region Get and set nickname_ + + const common::optional& nickname() const override; + + common::optional& mutable_nickname() override; + + void set_nickname( + common::optional value + ) override; + + // endregion + + ~SomeItem() override = default; + + private: + std::wstring name_; + + common::optional nickname_; +}; + +class SpecialItem + : public ISpecialItem { + public: + SpecialItem( + std::wstring name, + int64_t level, + common::optional nickname = common::nullopt + ); + + ModelType model_type() const override; + + // region Get and set name_ + + const std::wstring& name() const override; + + std::wstring& mutable_name() override; + + void set_name( + std::wstring value + ) override; + + // endregion + + // region Get and set nickname_ + + const common::optional& nickname() const override; + + common::optional& mutable_nickname() override; + + void set_nickname( + common::optional value + ) override; + + // endregion + + // region Get and set level_ + + int64_t level() const override; + + int64_t& mutable_level() override; + + void set_level( + int64_t value + ) override; + + // endregion + + ~SpecialItem() override = default; + + private: + std::wstring name_; + + common::optional nickname_; + + int64_t level_; +}; + +class Container + : public IContainer { + public: + Container( + std::vector< + std::shared_ptr + > items, + std::vector< + std::shared_ptr + > some_items, + common::optional< + std::vector< + std::shared_ptr + > + > special_items = common::nullopt + ); + + ModelType model_type() const override; + + // region Get and set items_ + + const std::vector< + std::shared_ptr + >& items() const override; + + std::vector< + std::shared_ptr + >& mutable_items() override; + + void set_items( + std::vector< + std::shared_ptr + > value + ) override; + + // endregion + + // region Get and set some_items_ + + const std::vector< + std::shared_ptr + >& some_items() const override; + + std::vector< + std::shared_ptr + >& mutable_some_items() override; + + void set_some_items( + std::vector< + std::shared_ptr + > value + ) override; + + // endregion + + // region Get and set special_items_ + + const common::optional< + std::vector< + std::shared_ptr + > + >& special_items() const override; + + common::optional< + std::vector< + std::shared_ptr + > + >& mutable_special_items() override; + + void set_special_items( + common::optional< + std::vector< + std::shared_ptr + > + > value + ) override; + + // endregion + + int64_t CountItems( + const std::vector< + std::shared_ptr + >& items + ) const override; + + ~Container() override = default; + + private: + std::vector< + std::shared_ptr + > items_; + + std::vector< + std::shared_ptr + > some_items_; + + common::optional< + std::vector< + std::shared_ptr + > + > special_items_; +}; + +class Wrapper + : public IWrapper { + public: + explicit Wrapper( + std::shared_ptr container + ); + + ModelType model_type() const override; + + // region Get and set container_ + + const std::shared_ptr& container() const override; + + std::shared_ptr& mutable_container() override; + + void set_container( + std::shared_ptr value + ) override; + + // endregion + + ~Wrapper() override = default; + + private: + std::shared_ptr container_; +}; + +// endregion + +// region Is-a functions + +/** + * \brief Check whether \p that instance is of runtime type + * \ref IItem. + * + * We use `IClass::model_type` to determine the runtime type, which is + * a bit faster than native C++'s RTTI. + * + * \param that instance to check for runtime type + * \return `true` if \p that instance is indeed + * an instance of \ref IItem + */ +bool IsItem( + const IClass& that +); + +/** + * \brief Check whether \p that instance is of runtime type + * \ref ISomeItem. + * + * We use `IClass::model_type` to determine the runtime type, which is + * a bit faster than native C++'s RTTI. + * + * \param that instance to check for runtime type + * \return `true` if \p that instance is indeed + * an instance of \ref ISomeItem + */ +bool IsSomeItem( + const IClass& that +); + +/** + * \brief Check whether \p that instance is of runtime type + * \ref ISpecialItem. + * + * We use `IClass::model_type` to determine the runtime type, which is + * a bit faster than native C++'s RTTI. + * + * \param that instance to check for runtime type + * \return `true` if \p that instance is indeed + * an instance of \ref ISpecialItem + */ +bool IsSpecialItem( + const IClass& that +); + +/** + * \brief Check whether \p that instance is of runtime type + * \ref IContainer. + * + * We use `IClass::model_type` to determine the runtime type, which is + * a bit faster than native C++'s RTTI. + * + * \param that instance to check for runtime type + * \return `true` if \p that instance is indeed + * an instance of \ref IContainer + */ +bool IsContainer( + const IClass& that +); + +/** + * \brief Check whether \p that instance is of runtime type + * \ref IWrapper. + * + * We use `IClass::model_type` to determine the runtime type, which is + * a bit faster than native C++'s RTTI. + * + * \param that instance to check for runtime type + * \return `true` if \p that instance is indeed + * an instance of \ref IWrapper + */ +bool IsWrapper( + const IClass& that +); + +// endregion Is-a functions + +} // namespace types +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_TYPES_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/verification.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/verification.hpp new file mode 100644 index 000000000..97dd9b21f --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/verification.hpp @@ -0,0 +1,884 @@ +#ifndef DUMMY_VERIFICATION_GUARD_ +#define DUMMY_VERIFICATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/constants.hpp" +#include "dummy/iteration.hpp" +#include "dummy/pattern.hpp" +#include "dummy/stringification.hpp" +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup verification Verify that instances conform to the meta-model constraints. + * @{ + */ +namespace verification { + +// region Forward declarations +class Iterator; +class IVerification; + +namespace impl { +class IVerificator; +} // namespace impl +// endregion Forward declarations + +/** + * Represent a verification error in an instance. + */ +struct Error { + /** + * Human-readable description of the error + */ + std::wstring cause; + + /** + * Path to the erroneous value + */ + iteration::Path path; + + explicit Error(std::wstring a_cause); + Error(std::wstring a_cause, iteration::Path a_path); +}; // struct Error + +/** + * \brief Iterate over the verification errors. + * + * The user is expected to take ownership of the errors if they need to be further + * processed. + * + * Unlike STL, this is not a light-weight iterator. We implement + * a "yielding" iterator which keeps where it stopped in the model, so that + * it looks for the next error only when you move it. + * + * This means that copy-construction and equality comparisons are much more heavy-weight + * than you'd usually expect from an STL iterator. For example, if you want to sort + * the errors by some criterion, you are most probably faster if you populate a vector, + * and then sort the vector. + * + * Also, given that this iterator is not light-weight, you should in almost all cases + * avoid the postfix increment (it++) and prefer the prefix one (++it) as the postfix + * increment would create an iterator copy every time. + * + * We follow the C++ standard, and assume that comparison between the two iterators + * over two different collections results in undefined behavior. See + * http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2948.html and + * https://stackoverflow.com/questions/4657513/comparing-iterators-from-different-containers. + */ +class Iterator { + using iterator_category = std::forward_iterator_tag; + /// The difference is meaningless, but has to be defined. + using difference_type = std::ptrdiff_t; + using value_type = Error; + using pointer = const Error*; + using reference = const Error&; + + public: + explicit Iterator( + std::unique_ptr verificator + ) : + verificator_(std::move(verificator)) { + // Intentionally empty. + } + + Iterator(const Iterator& other); + Iterator(Iterator&& other); + + Iterator& operator=(const Iterator& other); + Iterator& operator=(Iterator&& other); + + reference operator*() const; + pointer operator->() const; + + // Prefix increment + Iterator& operator++(); + + // Postfix increment + Iterator operator++(int); + + friend bool operator==(const Iterator& a, const Iterator& b); + friend bool operator!=(const Iterator& a, const Iterator& b); + + private: + std::unique_ptr verificator_; +}; + +bool operator==(const Iterator& a, const Iterator& b); + +bool operator!=(const Iterator& a, const Iterator& b); + +/// \cond HIDDEN +namespace impl { +class IVerificator { + public: + virtual void Start() = 0; + virtual void Next() = 0; + virtual bool Done() const = 0; + + virtual const Error& Get() const = 0; + virtual Error& GetMutable() = 0; + virtual long Index() const = 0; + + virtual std::unique_ptr Clone() const = 0; + + virtual ~IVerificator() = default; +}; // class IVerificator +} // namespace impl +/// \endcond + +class IVerification { + public: + virtual Iterator begin() const = 0; + virtual const Iterator& end() const = 0; + virtual ~IVerification() = default; +}; // class IVerification + +/** + * \brief Verify that the instance conforms to the meta-model constraints. + * + * Do not proceed to verify the instances referenced from + * the given instance. + * + * Range-based loops should fit the vast majority of the use cases: + * \code + * std::shared_ptr env = ...; + * for (const Error& error : NonRecursiveVerification(env)) { + * report_somehow(error); + * } + * \endcode + * + * We use const references to shared pointers here for efficiency. Since + * we do not make a copy of \p that shared pointer, it is very important that + * the given shared pointer outlives the verification, lest cause undefined behavior. + * See these StackOverflow questions: + * * https://stackoverflow.com/questions/12002480/passing-stdshared-ptr-to-constructors/12002668#12002668 + * * https://stackoverflow.com/questions/3310737/should-we-pass-a-shared-ptr-by-reference-or-by-value + * * https://stackoverflow.com/questions/37610494/passing-const-shared-ptrt-versus-just-shared-ptrt-as-parameter + */ +class NonRecursiveVerification : public IVerification { + public: + NonRecursiveVerification( + const std::shared_ptr& instance + ); + + Iterator begin() const override; + const Iterator& end() const override; + + ~NonRecursiveVerification() override = default; + private: + const std::shared_ptr& instance_; +}; // class NonRecursiveVerification + +/** + * \brief Verify that the instance conforms to the meta-model constraints. + * + * Also verify recursively all the instances referenced from + * the given instance. + * + * Range-based loops should fit the vast majority of the use cases: + * \code + * std::shared_ptr env = ...; + * for (const Error& error : RecursiveVerification(env)) { + * report_somehow(error); + * } + * \endcode + * + * We use const references to shared pointers here for efficiency. Since + * we do not make a copy of \p that shared pointer, it is very important that + * the given shared pointer outlives the verification, lest cause undefined behavior. + * See these StackOverflow questions: + * * https://stackoverflow.com/questions/12002480/passing-stdshared-ptr-to-constructors/12002668#12002668 + * * https://stackoverflow.com/questions/3310737/should-we-pass-a-shared-ptr-by-reference-or-by-value + * * https://stackoverflow.com/questions/37610494/passing-const-shared-ptrt-versus-just-shared-ptrt-as-parameter + */ +class RecursiveVerification : public IVerification { + public: + RecursiveVerification( + const std::shared_ptr& instance + ); + + Iterator begin() const override; + const Iterator& end() const override; + + ~RecursiveVerification() override = default; + private: + const std::shared_ptr& instance_; +}; // class RecursiveVerification + +// region Verification functions + +/// \brief Check that the names of the \p items are unique. +/// +/// The \p items are a generic sequence, so the sequences of +/// the descendants of types::IItem can be passed without a copy. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool NamesAreUnique( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Iterate over a generic sequence in a generator. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool NamesAreNonEmpty( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Narrow the items of a generic sequence in a generator. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool SomeSpecialItemHasLevel( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Index a generic sequence, and narrow the item. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool FirstIsSpecial( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Check that the nicknames of the \p items are unique, if specified. +/// +/// This mirrors the body of `ID_shorts_are_unique` in aas-core-meta V3.2: +/// a local variable takes an optional property of an item of a generic sequence, +/// and the loop continues if it is None. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool NicknamesAreUnique( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Narrow the loop variable over a generic sequence in a for-loop. +/// +/// The loop variable is of the type parameter in Go, so the down-cast needs to +/// go through `any(...)`, and of the template parameter in C++. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool LevelsOfSpecialItemsArePositive( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Narrow an immutable local variable which holds an item of a generic sequence. +/// +/// In contrast to FirstIsSpecial, the local variable is annotated, so +/// it is of the parent class instead of the type parameter. Hence, Go down-casts +/// it without `any(...)`, and C++ binds the item to a constant reference to +/// the shared pointer of the parent class. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool LastIsSpecial( + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Mix two generic sequences with an argument which is not generic. +/// +/// The names must differ, except for the \p shared_name, which may repeat. +/// +/// Go declares two type parameters, and C++ two template parameters. The nested +/// generators iterate over two different generic sequences, so their variables +/// are of two different type parameters. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +/// +/// \tparam OtherItemsT type of the items of \p other_items, types::ISomeItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT, + typename OtherItemsT +> +bool NamesAreDisjoint( + const std::vector< + std::shared_ptr + >& items, + const std::vector< + std::shared_ptr + >& other_items, + const std::wstring& shared_name +); + +/// \brief Use the immutable aliases of the generic sequences. +/// +/// An immutable alias of the very same type shares the type annotation of +/// the aliased generic sequence, so it is a generic sequence itself. Go declares +/// the alias with the type parameter, C++ with the template parameter, while C# +/// and Java infer its type with `var`. The `alias_of_alias` aliases an alias, +/// and the `optional_alias` aliases an optional generic sequence. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +/// +/// \tparam OptionalItemsT type of the items of \p optional_items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT, + typename OptionalItemsT +> +bool AliasedNamesAreUnique( + const std::vector< + std::shared_ptr + >& items, + const common::optional< + std::vector< + std::shared_ptr + > + >& optional_items +); + +/// \brief Look up an instance of a descendant in a generic sequence. +/// +/// Go compares the instances as `any`'s, as the items are of the type parameter, +/// and C++ converts the member to the shared pointer of types::IItem so that +/// the raw pointers are comparable for any template parameter. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool ContainsSomeItem( + const std::shared_ptr& some_item, + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Look up the items of a generic sequence in another generic sequence. +/// +/// The member and the items are of two different type parameters. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +/// +/// \tparam OtherItemsT type of the items of \p other_items, types::ISomeItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT, + typename OtherItemsT +> +bool ItemsOverlap( + const std::vector< + std::shared_ptr + >& items, + const std::vector< + std::shared_ptr + >& other_items +); + +/// \brief Look up an instance in a sequence which is not generic. +/// +/// The types::ISpecialItem has no descendants, so the \p items are not +/// a generic sequence, but Go still compares the instances as `any`'s. +bool ContainsSpecialItem( + const std::shared_ptr& special_item, + const std::vector< + std::shared_ptr + >& items +); + +/// \brief Pass the local variables on to generic sequences. +/// +/// The `some_items` is a read-only alias without an annotation, while +/// the `all_some_items` is an immutable local variable declared with +/// `Final[...]`. Both hold the sequences of types::ISomeItem, and are passed +/// on to the generic sequences of types::IItem, required as well as optional. +bool ContainerNamesAreUnique( + const std::shared_ptr& container +); + +/// \brief Pass an optional generic sequence on to a generic sequence. +/// +/// \tparam ItemsT type of the items of \p items, types::IItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename ItemsT +> +bool OptionalNamesAreUnique( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +); + +/// \brief Pass the generic sequences of the descendants on. +/// +/// The \p some_items are passed on as a required and as an optional +/// sequence of the parent class, while the \p special_items are passed on +/// as an optional sequence and, once narrowed, as a required sequence of the parent +/// class of the parent class. +/// +/// \tparam SomeItemsT type of the items of \p some_items, types::ISomeItem or one of its descendants, so that the vectors of the descendants can be passed without a copy, as std::vector is invariant +template< + typename SomeItemsT +> +bool SomeNamesAreUnique( + const std::vector< + std::shared_ptr + >& some_items, + const common::optional< + std::vector< + std::shared_ptr + > + >& special_items +); + +/// \brief Check that the levels are positive. +/// +/// The types::ISpecialItem has no descendants, so the \p items are not +/// a generic sequence. +bool LevelsArePositive( + const std::vector< + std::shared_ptr + >& items +); + +// endregion Verification functions + +// region Definitions of generic verification functions + +template< + typename ItemsT +> +bool NamesAreUnique( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + std::unordered_set name_set = std::unordered_set(); + for (const std::shared_ptr& item : items) { + if (name_set.find(item->name()) != name_set.end()) { + return false; + } + name_set.insert(item->name()); + } + return true; +} + +template< + typename ItemsT +> +bool NamesAreNonEmpty( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + return ( + common::All( + [&](const std::shared_ptr& item) -> bool { + return common::LenStr(item->name()) > 0; + }, + items + ) + ); +} + +template< + typename ItemsT +> +bool SomeSpecialItemHasLevel( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + return ( + common::Some( + [&](const std::shared_ptr& item) -> bool { + return ( + types::IsSpecialItem(*item) + && std::dynamic_pointer_cast(item)->level() > 0 + ); + }, + items + ) + ); +} + +template< + typename ItemsT +> +bool FirstIsSpecial( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + if (items.size() == 0) { + return false; + } + auto first = items.at(0); + if (types::IsSpecialItem(*first)) { + return ( + std::dynamic_pointer_cast(first)->level() > 0 + ); + } + return false; +} + +template< + typename ItemsT +> +bool NicknamesAreUnique( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + std::unordered_set nickname_set = std::unordered_set(); + for (const std::shared_ptr& item : items) { + auto nickname = item->nickname(); + if (!(nickname.has_value())) { + continue; + } + if (nickname_set.find((*nickname)) != nickname_set.end()) { + return false; + } + nickname_set.insert((*nickname)); + } + return true; +} + +template< + typename ItemsT +> +bool LevelsOfSpecialItemsArePositive( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + for (const std::shared_ptr& item : items) { + if (types::IsSpecialItem(*item)) { + if (std::dynamic_pointer_cast(item)->level() <= 0) { + return false; + } + } + } + return true; +} + +template< + typename ItemsT +> +bool LastIsSpecial( + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + if (items.size() == 0) { + return false; + } + const std::shared_ptr last = items.at(items.size() - (1)); + if (types::IsSpecialItem(*last)) { + return ( + std::dynamic_pointer_cast(last)->level() > 0 + ); + } + return false; +} + +template< + typename ItemsT, + typename OtherItemsT +> +bool NamesAreDisjoint( + const std::vector< + std::shared_ptr + >& items, + const std::vector< + std::shared_ptr + >& other_items, + const std::wstring& shared_name +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + static_assert( + std::is_base_of::value, + "Expected the items of other_items to be instances of types::ISomeItem" + ); + return ( + common::All( + [&](const std::shared_ptr& item) -> bool { + return common::All( + [&](const std::shared_ptr& other_item) -> bool { + return ( + item->name() != other_item->name() + || item->name() == shared_name + ); + }, + other_items + ); + }, + items + ) + ); +} + +template< + typename ItemsT, + typename OptionalItemsT +> +bool AliasedNamesAreUnique( + const std::vector< + std::shared_ptr + >& items, + const common::optional< + std::vector< + std::shared_ptr + > + >& optional_items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + static_assert( + std::is_base_of::value, + "Expected the items of optional_items to be instances of types::IItem" + ); + const std::vector >& alias = items; + const std::vector >& alias_of_alias = alias; + const common::optional > >& optional_alias = optional_items; + for (const std::shared_ptr& item : alias) { + if (types::IsSpecialItem(*item)) { + if (std::dynamic_pointer_cast(item)->level() <= 0) { + return false; + } + } + } + return ( + ( + common::All( + [&](const std::shared_ptr& item) -> bool { + return common::LenStr(item->name()) > 0; + }, + alias_of_alias + ) + && NamesAreUnique( + alias_of_alias + ) + && OptionalNamesAreUnique( + optional_alias + ) + && ( + ( + (!(optional_alias.has_value())) + || NamesAreUnique( + (*optional_alias) + ) + ) + ) + ) + ); +} + +template< + typename ItemsT +> +bool ContainsSomeItem( + const std::shared_ptr& some_item, + const std::vector< + std::shared_ptr + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + return ( + common::Contains( + items, + std::shared_ptr( + some_item + ) + ) + ); +} + +template< + typename ItemsT, + typename OtherItemsT +> +bool ItemsOverlap( + const std::vector< + std::shared_ptr + >& items, + const std::vector< + std::shared_ptr + >& other_items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + static_assert( + std::is_base_of::value, + "Expected the items of other_items to be instances of types::ISomeItem" + ); + return ( + common::Some( + [&](const std::shared_ptr& other_item) -> bool { + return common::Contains( + items, + std::shared_ptr( + other_item + ) + ); + }, + other_items + ) + ); +} + +template< + typename ItemsT +> +bool OptionalNamesAreUnique( + const common::optional< + std::vector< + std::shared_ptr + > + >& items +) { + static_assert( + std::is_base_of::value, + "Expected the items of items to be instances of types::IItem" + ); + return ( + ( + (!(items.has_value())) + || NamesAreUnique( + (*items) + ) + ) + ); +} + +template< + typename SomeItemsT +> +bool SomeNamesAreUnique( + const std::vector< + std::shared_ptr + >& some_items, + const common::optional< + std::vector< + std::shared_ptr + > + >& special_items +) { + static_assert( + std::is_base_of::value, + "Expected the items of some_items to be instances of types::ISomeItem" + ); + return ( + ( + NamesAreUnique( + some_items + ) + && OptionalNamesAreUnique( + common::make_optional( + some_items + ) + ) + && OptionalNamesAreUnique( + special_items + ) + && OptionalNamesAreUnique( + common::optional< + std::vector< + std::shared_ptr + > + >() + ) + && ( + ( + (!(special_items.has_value())) + || NamesAreUnique( + (*special_items) + ) + ) + ) + ) + ); +} + +// endregion Definitions of generic verification functions + +} // namespace verification +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_VERIFICATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/visitation.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/visitation.hpp new file mode 100644 index 000000000..9b729636a --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/visitation.hpp @@ -0,0 +1,103 @@ +#ifndef DUMMY_VISITATION_GUARD_ +#define DUMMY_VISITATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/types.hpp" + +namespace dummy { + +/** + * \defgroup visitation Iterate and modify instances through visitors. + * @{ + */ +namespace visitation { + +/** + * Provide an interface for a recursive mutating visitor on an instance. + */ +class IVisitor { + public: + /** + * Visit \p that instance and recursively visit all the instances + * referenced from \p that instance. + * + * We use const references to shared pointers here for efficiency in case you want, + * say, to share ownership over instances in your own external containers. Since + * we do not make copies of the shared pointers, it is very important that + * the given shared pointers outlive the visitor, lest cause undefined behavior. + * See these StackOverflow questions: + * * https://stackoverflow.com/questions/12002480/passing-stdshared-ptr-to-constructors/12002668#12002668 + * * https://stackoverflow.com/questions/3310737/should-we-pass-a-shared-ptr-by-reference-or-by-value + * * https://stackoverflow.com/questions/37610494/passing-const-shared-ptrt-versus-just-shared-ptrt-as-parameter + * + * Changing the references during the visitation results in undefined + * behavior. This follows how STL deals with modifications to containers, see: + * https://stackoverflow.com/questions/6438086/iterator-invalidation-rules-for-c-containers + * + * \param that instance to be visited recursively + */ + virtual void Visit(const std::shared_ptr& that) = 0; + virtual ~IVisitor() = default; + + protected: + virtual void VisitSomeItem( + const std::shared_ptr& that + ) = 0; + virtual void VisitSpecialItem( + const std::shared_ptr& that + ) = 0; + virtual void VisitContainer( + const std::shared_ptr& that + ) = 0; + virtual void VisitWrapper( + const std::shared_ptr& that + ) = 0; +}; // class IVisitor + +/** + * Provide an abstract recursive mutating visitor on an instance. + */ +class AbstractVisitor + : public IVisitor { + public: + void Visit(const std::shared_ptr& that) override; + ~AbstractVisitor() override = default; +}; // class AbstractVisitor + +/** + * \brief Provide a mutating, recursive and no-op visitor on an instance. + * + * Usually, you want to inherit from this visitor and override one or more of its + * visitation methods. + */ +class PassThroughVisitor + : public AbstractVisitor { + public: + ~PassThroughVisitor() override = default; + + protected: + void VisitSomeItem( + const std::shared_ptr& that + ) override; + void VisitSpecialItem( + const std::shared_ptr& that + ) override; + void VisitContainer( + const std::shared_ptr& that + ) override; + void VisitWrapper( + const std::shared_ptr& that + ) override; +}; // class PassThroughVisitor + +} // namespace visitation +/**@*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_VISITATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/wstringification.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/wstringification.hpp new file mode 100644 index 000000000..b5d9042c7 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/wstringification.hpp @@ -0,0 +1,63 @@ +#ifndef DUMMY_WSTRINGIFICATION_GUARD_ +#define DUMMY_WSTRINGIFICATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/types.hpp" + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup wstringification De/wstringify to and from wstrings (where applicable). + * @{ + */ +namespace wstringification { + +/** + * Try to parse the \p text as a model type literal. + * + * \param text to be parsed + * \return literal, or nothing, if \p text invalid + */ +common::optional ModelTypeFromWstring( + const std::wstring& text +); + +/** + * Parse the \p text as a model type literal. + * + * \param text to be parsed + * \return literal + * \throw std::invalid_argument if \p text invalid + */ +types::ModelType MustModelTypeFromWstring( + const std::wstring& text +); + +/** + * Translate the enumeration literal \p model_type to text. + * + * \param model_type to be converted into text + * \return text representation of \p model_type + * \throw std::invalid_argument if \p model_type invalid + */ +std::wstring to_wstring( + types::ModelType model_type +); + +} // namespace wstringification +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_WSTRINGIFICATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xml_path.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xml_path.hpp new file mode 100644 index 000000000..ce72feb1f --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xml_path.hpp @@ -0,0 +1,136 @@ +#ifndef DUMMY_XML_PATH_GUARD_ +#define DUMMY_XML_PATH_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup xml_path Report where in an XML document an error occurred. + * @{ + */ +namespace xml_path { + +/** + * Represent a segment of an XPath to an erroneous value. + */ +class ISegment { + public: + /** + * \brief Convert the segment to a string in an XPath. + * + * The result is escaped such that it can be directly inserted + * into an XPath. + */ + virtual std::wstring ToWstring() const = 0; + + virtual std::unique_ptr Clone() const = 0; + + virtual ~ISegment() = default; +}; // class ISegment + +/** + * Represent an element on an XPath to the erroneous value. + */ +struct ElementSegment : public ISegment { + /** + * \brief Name of the XML element, without the namespace + * + * We deliberately omit the namespace in the tag names. If you want to actually + * query with the resulting XPath, you have to insert the namespaces manually. + * We did not know how to include the namespace in a meaningful way, as XPath + * assumes namespace prefixes to be defined outside of the document. + * At least the path thus rendered is informative, and you should be able to + * descend it manually. + */ + std::wstring name; + + ElementSegment( + std::wstring a_name + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~ElementSegment() override = default; +}; // struct ElementSegment + +/** + * \brief Represent a member of a struct on an XPath to the erroneous value. + * + * A struct holds its members' names in a ```` child element instead of + * an attribute, so we have to match on that child element. + */ +struct MemberSegment : public ISegment { + /** + * Name of the member + */ + std::wstring name; + + explicit MemberSegment( + std::wstring a_name + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~MemberSegment() override = default; +}; // struct MemberSegment + +/** + * Represent an element in a sequence on an XPath to the erroneous value. + */ +struct IndexSegment : public ISegment { + /** + * Zero-based index of the element in the sequence + * + * \note XPath counts from one, so we render the index plus one. + */ + size_t index; + + explicit IndexSegment( + size_t an_index + ); + + std::wstring ToWstring() const override; + + std::unique_ptr Clone() const override; + + ~IndexSegment() override = default; +}; // struct IndexSegment + +/** + * Represent the relative XPath to the erroneous element. + */ +struct Path { + std::deque > segments; + + Path(); + Path(const Path& other); + Path(Path&& other); + Path& operator=(const Path& other); + Path& operator=(Path&& other); + + std::wstring ToWstring() const; +}; // struct Path + +} // namespace xml_path +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_XML_PATH_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xmlization.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xmlization.hpp new file mode 100644 index 000000000..afb303290 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/include/dummy/xmlization.hpp @@ -0,0 +1,245 @@ +#ifndef DUMMY_XMLIZATION_GUARD_ +#define DUMMY_XMLIZATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/iteration.hpp" +#include "dummy/types.hpp" +#include "dummy/xml_path.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +/** + * \defgroup xmlization De/serialize instances from and to XML. + * @{ + */ +namespace xmlization { + +/** + * Specify the expected XML namespace of all the XML elements. + */ +extern const std::string kNamespace; + +// region De-serialization + +/** + * Represent a de-serialization error. + */ +struct DeserializationError { + /** + * Human-readable description of the error + */ + std::wstring cause; + + /** + * Path to the erroneous value + */ + xml_path::Path path; + + explicit DeserializationError(std::wstring a_cause); + DeserializationError(std::wstring a_cause, xml_path::Path a_path); +}; // struct DeserializationError + +struct ReadingOptions { + /** + * No XML attributes are expected in XML elements. + * Usually, attributes are considered errors and reported as such. However, + * some implementations add their own custom attributes, and we sometimes + * still want to parse such XML. If `additional_attributes` is set, + * the unexpected XML attributes will be ignored during parsing, and not + * reported. + */ + bool additional_attributes = false; + + /** + * Size of the chunk to be read from the input stream and passed to + * the XML parser. + */ + size_t buffer_size = 1024; +}; // struct ReadingOptions + +/** + * Deserialize the instance from an XML read from the stream \p is. + * + * \param is stream of ASCII, ISO-8859-1 or UTF-8-encoded characters to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed instance, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> From( + std::istream& is, + const ReadingOptions& options = {} +); + +/** + * Deserialize an instance of types::IItem from an XML + * read from the stream \p is. + * + * \param is stream to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed types::IItem, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> ItemFrom( + std::istream& is, + const ReadingOptions& options = {} +); + +/** + * Deserialize an instance of types::ISomeItem from an XML + * read from the stream \p is. + * + * \param is stream to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed types::ISomeItem, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> SomeItemFrom( + std::istream& is, + const ReadingOptions& options = {} +); + +/** + * Deserialize an instance of types::ISpecialItem from an XML + * read from the stream \p is. + * + * \param is stream to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed types::ISpecialItem, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> SpecialItemFrom( + std::istream& is, + const ReadingOptions& options = {} +); + +/** + * Deserialize an instance of types::IContainer from an XML + * read from the stream \p is. + * + * \param is stream to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed types::IContainer, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> ContainerFrom( + std::istream& is, + const ReadingOptions& options = {} +); + +/** + * Deserialize an instance of types::IWrapper from an XML + * read from the stream \p is. + * + * \param is stream to read XML from + * \param options reading options to be tweaked for special cases. The defaults should + * work in most cases. + * \return the parsed types::IWrapper, or an error if any + */ +common::expected< + std::shared_ptr, + DeserializationError +> WrapperFrom( + std::istream& is, + const ReadingOptions& options = {} +); + +// endregion Deserialization + +// region Serialization + +/** + * Represent an error in the serialization of an instance to XML. + */ +class SerializationException : public std::exception { + public: + SerializationException( + std::wstring cause + ); + + SerializationException( + std::wstring cause, + iteration::Path path + ); + + const char* what() const noexcept override; + + const std::wstring& cause() const noexcept; + const iteration::Path& path() const noexcept; + + ~SerializationException() noexcept override = default; + + private: + const std::wstring cause_; + const iteration::Path path_; + const std::string msg_; +}; // class SerializationException + +/** + * \brief Customize how instances should be serialized to XML. + * + * We selected the defaults so that they can be used when you serialize to + * a file. + */ +struct WritingOptions { + /** + * If set, the XML declaration is written at the beginning. + */ + bool write_declaration = true; + + /** + * If set, the root XML element is written with the XML namespace + * set as the XML attribute `xmlns`. + */ + bool write_namespace = true; +}; // struct WritingOptions + +/** + * \brief Serialize \p that instance to XML. + * + * \param that instance to be serialized + * \param options to be tweaked for special cases. The defaults should + * work in most cases. + * \param os The UTF8-encoded output stream where XML will be written + * \throw \ref SerializationException if \p that instance could not be serialized + */ +void Serialize( + const types::IClass& that, + const WritingOptions& options, + std::ostream& os +); + +// endregion Serialization + +} // namespace xmlization +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_XMLIZATION_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/common.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/common.cpp new file mode 100644 index 000000000..3b1bb5237 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/common.cpp @@ -0,0 +1,14232 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +// NOTE (mristin): +// We use MultiByteToWideChar and WideCharToMulitByte from on Windows +// as std::codecvt is not robust enough on Windows, and produces invalid UTF-16 +// sequences as std::wstring. See: +// https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t, +// especially the comment: +// https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t#comment110447503_18597384 +#ifdef _WIN32 +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) +#else +// NOTE (mristin): +// We use codecvt although it has been deprecated. There has been not +// suitable replacement proposed yet, so we simply stick to it +// until there is. See: +// https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) +#endif + +namespace dummy { +namespace common { + +std::string Concat( + const std::string& part0, + const std::string& part1 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61, + const std::string& part62 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + size += part62.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + result.append(part62); + + return result; +} + +std::string Concat( + const std::string& part0, + const std::string& part1, + const std::string& part2, + const std::string& part3, + const std::string& part4, + const std::string& part5, + const std::string& part6, + const std::string& part7, + const std::string& part8, + const std::string& part9, + const std::string& part10, + const std::string& part11, + const std::string& part12, + const std::string& part13, + const std::string& part14, + const std::string& part15, + const std::string& part16, + const std::string& part17, + const std::string& part18, + const std::string& part19, + const std::string& part20, + const std::string& part21, + const std::string& part22, + const std::string& part23, + const std::string& part24, + const std::string& part25, + const std::string& part26, + const std::string& part27, + const std::string& part28, + const std::string& part29, + const std::string& part30, + const std::string& part31, + const std::string& part32, + const std::string& part33, + const std::string& part34, + const std::string& part35, + const std::string& part36, + const std::string& part37, + const std::string& part38, + const std::string& part39, + const std::string& part40, + const std::string& part41, + const std::string& part42, + const std::string& part43, + const std::string& part44, + const std::string& part45, + const std::string& part46, + const std::string& part47, + const std::string& part48, + const std::string& part49, + const std::string& part50, + const std::string& part51, + const std::string& part52, + const std::string& part53, + const std::string& part54, + const std::string& part55, + const std::string& part56, + const std::string& part57, + const std::string& part58, + const std::string& part59, + const std::string& part60, + const std::string& part61, + const std::string& part62, + const std::string& part63 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + size += part62.size(); + size += part63.size(); + + std::string result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + result.append(part62); + result.append(part63); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61, + const std::wstring& part62 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + size += part62.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + result.append(part62); + + return result; +} + +std::wstring Concat( + const std::wstring& part0, + const std::wstring& part1, + const std::wstring& part2, + const std::wstring& part3, + const std::wstring& part4, + const std::wstring& part5, + const std::wstring& part6, + const std::wstring& part7, + const std::wstring& part8, + const std::wstring& part9, + const std::wstring& part10, + const std::wstring& part11, + const std::wstring& part12, + const std::wstring& part13, + const std::wstring& part14, + const std::wstring& part15, + const std::wstring& part16, + const std::wstring& part17, + const std::wstring& part18, + const std::wstring& part19, + const std::wstring& part20, + const std::wstring& part21, + const std::wstring& part22, + const std::wstring& part23, + const std::wstring& part24, + const std::wstring& part25, + const std::wstring& part26, + const std::wstring& part27, + const std::wstring& part28, + const std::wstring& part29, + const std::wstring& part30, + const std::wstring& part31, + const std::wstring& part32, + const std::wstring& part33, + const std::wstring& part34, + const std::wstring& part35, + const std::wstring& part36, + const std::wstring& part37, + const std::wstring& part38, + const std::wstring& part39, + const std::wstring& part40, + const std::wstring& part41, + const std::wstring& part42, + const std::wstring& part43, + const std::wstring& part44, + const std::wstring& part45, + const std::wstring& part46, + const std::wstring& part47, + const std::wstring& part48, + const std::wstring& part49, + const std::wstring& part50, + const std::wstring& part51, + const std::wstring& part52, + const std::wstring& part53, + const std::wstring& part54, + const std::wstring& part55, + const std::wstring& part56, + const std::wstring& part57, + const std::wstring& part58, + const std::wstring& part59, + const std::wstring& part60, + const std::wstring& part61, + const std::wstring& part62, + const std::wstring& part63 +) { + size_t size = 0; + size += part0.size(); + size += part1.size(); + size += part2.size(); + size += part3.size(); + size += part4.size(); + size += part5.size(); + size += part6.size(); + size += part7.size(); + size += part8.size(); + size += part9.size(); + size += part10.size(); + size += part11.size(); + size += part12.size(); + size += part13.size(); + size += part14.size(); + size += part15.size(); + size += part16.size(); + size += part17.size(); + size += part18.size(); + size += part19.size(); + size += part20.size(); + size += part21.size(); + size += part22.size(); + size += part23.size(); + size += part24.size(); + size += part25.size(); + size += part26.size(); + size += part27.size(); + size += part28.size(); + size += part29.size(); + size += part30.size(); + size += part31.size(); + size += part32.size(); + size += part33.size(); + size += part34.size(); + size += part35.size(); + size += part36.size(); + size += part37.size(); + size += part38.size(); + size += part39.size(); + size += part40.size(); + size += part41.size(); + size += part42.size(); + size += part43.size(); + size += part44.size(); + size += part45.size(); + size += part46.size(); + size += part47.size(); + size += part48.size(); + size += part49.size(); + size += part50.size(); + size += part51.size(); + size += part52.size(); + size += part53.size(); + size += part54.size(); + size += part55.size(); + size += part56.size(); + size += part57.size(); + size += part58.size(); + size += part59.size(); + size += part60.size(); + size += part61.size(); + size += part62.size(); + size += part63.size(); + + std::wstring result; + result.reserve(size); + + result.append(part0); + result.append(part1); + result.append(part2); + result.append(part3); + result.append(part4); + result.append(part5); + result.append(part6); + result.append(part7); + result.append(part8); + result.append(part9); + result.append(part10); + result.append(part11); + result.append(part12); + result.append(part13); + result.append(part14); + result.append(part15); + result.append(part16); + result.append(part17); + result.append(part18); + result.append(part19); + result.append(part20); + result.append(part21); + result.append(part22); + result.append(part23); + result.append(part24); + result.append(part25); + result.append(part26); + result.append(part27); + result.append(part28); + result.append(part29); + result.append(part30); + result.append(part31); + result.append(part32); + result.append(part33); + result.append(part34); + result.append(part35); + result.append(part36); + result.append(part37); + result.append(part38); + result.append(part39); + result.append(part40); + result.append(part41); + result.append(part42); + result.append(part43); + result.append(part44); + result.append(part45); + result.append(part46); + result.append(part47); + result.append(part48); + result.append(part49); + result.append(part50); + result.append(part51); + result.append(part52); + result.append(part53); + result.append(part54); + result.append(part55); + result.append(part56); + result.append(part57); + result.append(part58); + result.append(part59); + result.append(part60); + result.append(part61); + result.append(part62); + result.append(part63); + + return result; +} + +std::string WstringToUtf8(const std::wstring& text) { + #ifdef _WIN32 + // Inspired by: + // https://stackoverflow.com/a/69410299/1600678 + + if (text.empty()) { + return ""; + } + + // NOTE (mristin): + // We put `max` into parentheses to avoid conflicts with + // `max` macro, see: + // https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std + + const size_t text_size = text.size(); + if ( + text_size + > static_cast( + (std::numeric_limits::max)() + ) + ) { + throw std::out_of_range( + common::Concat( + "The size of the text to be converted to UTF-8, ", + std::to_string(text_size), + ", exceeds the maximum int value ", + std::to_string((std::numeric_limits::max)()) + ) + ); + } + const int text_size_int = static_cast(text_size); + + const auto size_needed = WideCharToMultiByte( + CP_UTF8, + 0, + &(text[0]), + text_size_int, + nullptr, + 0, + nullptr, + nullptr + ); + + if (size_needed <= 0) { + throw std::runtime_error( + "WideCharToMultiByte() failed: " + std::to_string(size_needed) + ); + } + + std::string result(size_needed, 0); + + WideCharToMultiByte( + CP_UTF8, + 0, + &(text[0]), + text_size_int, + &(result[0]), + size_needed, + nullptr, + nullptr + ); + + return result; + #else + // NOTE (mristin): + // We use codecvt although it has been deprecated. There has been not + // suitable replacement proposed yet, so we simply stick to it + // until there is. See: + // https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement + + std::wstring_convert > conv; + return conv.to_bytes(text.data()); + #endif +} + +std::wstring Utf8ToWstring( + const char* utf8_text, + size_t utf8_text_size +) { + if (utf8_text_size == 0) { + return std::wstring(); + } + + #ifdef _WIN32 + // NOTE (mristin): + // We have to use MultiByteToWideChar from on Windows + // as std::codecvt is not robust enough on Windows and produces invalid UTF-16 + // sequences as std::wstring. See: + // https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t, + // especially the comment: + // https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t#comment110447503_18597384 + + // Inspired by: + // https://stackoverflow.com/a/69410299/1600678 + + if (utf8_text_size == std::string::npos) { + utf8_text_size = strlen(utf8_text); + } + + // NOTE (mristin): + // We put `max` into parentheses to avoid conflicts with + // `max` macro, see: + // https://stackoverflow.com/questions/11544073/how-do-i-deal-with-the-max-macro-in-windows-h-colliding-with-max-in-std + + if ( + utf8_text_size + > static_cast( + (std::numeric_limits::max)() + ) + ) { + throw std::out_of_range( + common::Concat( + "The size of the UTF-8 text to be converted to wide string, ", + std::to_string(utf8_text_size), + ", exceeds the maximum int value ", + std::to_string((std::numeric_limits::max)()) + ) + ); + } + const int utf8_text_size_int = static_cast(utf8_text_size); + + const auto size_needed = MultiByteToWideChar( + CP_UTF8, + 0, + utf8_text, + utf8_text_size_int, + nullptr, + 0 + ); + if (size_needed <= 0) { + throw std::runtime_error( + "MultiByteToWideChar() failed: " + std::to_string(size_needed) + ); + } + + std::wstring result(size_needed, 0); + + MultiByteToWideChar( + CP_UTF8, + 0, + utf8_text, + utf8_text_size_int, + &(result[0]), + size_needed + ); + + return result; + + #else + // NOTE (mristin): + // We use codecvt although it has been deprecated. There has been not + // suitable replacement proposed yet, so we simply stick to it + // until there is. See: + // https://stackoverflow.com/questions/42946335/deprecated-header-codecvt-replacement + + std::wstring_convert,wchar_t> conv; + return conv.from_bytes(utf8_text); + #endif +} + +std::wstring Utf8ToWstring(const std::string& utf8_text) { + return Utf8ToWstring(&(utf8_text[0]), utf8_text.size()); +} + +namespace { + +// NOTE (mristin): +// The type wchar_t is a UTF-16 code unit on Windows, where a character beyond +// the Basic Multilingual Plane takes two of them (a surrogate pair). On the other +// platforms, wchar_t is a UTF-32 code unit, so that each character takes exactly +// one wchar_t. +#if WCHAR_MAX <= 0xFFFF +/** + * Check whether a surrogate pair starts at \p offset in \p text. + */ +bool IsSurrogatePairAt(const std::wstring& text, size_t offset) { + return ( + offset + 1 < text.size() + && text[offset] >= 0xD800 + && text[offset] <= 0xDBFF + && text[offset + 1] >= 0xDC00 + && text[offset + 1] <= 0xDFFF + ); +} + +/** + * Count the characters of \p text between \p start_offset and + * \p end_offset, both in wchar_t's. + */ +size_t CountCharacters( + const std::wstring& text, + size_t start_offset, + size_t end_offset +) { + size_t count = 0; + size_t offset = start_offset; + while (offset < end_offset) { + offset += IsSurrogatePairAt(text, offset) ? 2 : 1; + ++count; + } + + return count; +} + +/** + * Compute the offset in wchar_t's of the character at \p position + * in \p text. + */ +size_t OffsetOf(const std::wstring& text, size_t position) { + size_t offset = 0; + for (size_t i = 0; i < position; ++i) { + offset += IsSurrogatePairAt(text, offset) ? 2 : 1; + } + + return offset; +} +#else +/** + * Count the characters of a text between \p start_offset and + * \p end_offset, both in wchar_t's. + */ +size_t CountCharacters( + const std::wstring&, + size_t start_offset, + size_t end_offset +) { + return end_offset - start_offset; +} + +/** + * Compute the offset in wchar_t's of the character at \p position + * in a text. + */ +size_t OffsetOf(const std::wstring&, size_t position) { + return position; +} +#endif + +/** + * Resolve \p position in a string of \p length as Python does in slicing. + * + * A negative position counts from the end, and the positions out of range are + * clamped to the string. + * + * \param position to be resolved + * \param length of the string + * \return the resolved position within `[0, length]` + */ +size_t ResolvePosition(int64_t position, size_t length) { + const int64_t signed_length = static_cast(length); + + if (position < 0) { + const int64_t resolved = position + signed_length; + return resolved < 0 ? 0 : static_cast(resolved); + } + + return position > signed_length ? length : static_cast(position); +} + +} // namespace + +size_t LenStr(const std::wstring& text) { + return CountCharacters(text, 0, text.size()); +} + +std::wstring SliceStr( + const std::wstring& text, + int64_t start, + int64_t end +) { + const size_t length = LenStr(text); + const size_t the_start = ResolvePosition(start, length); + const size_t the_end = ResolvePosition(end, length); + + if (the_start >= the_end) { + return L""; + } + + const size_t start_offset = OffsetOf(text, the_start); + const size_t end_offset = OffsetOf(text, the_end); + return text.substr(start_offset, end_offset - start_offset); +} + +std::wstring SliceStr( + const std::wstring& text, + int64_t start +) { + return text.substr( + OffsetOf(text, ResolvePosition(start, LenStr(text))) + ); +} + +int64_t FindStr( + const std::wstring& text, + const std::wstring& sub +) { + return FindStr(text, sub, 0); +} + +int64_t FindStr( + const std::wstring& text, + const std::wstring& sub, + int64_t start +) { + const int64_t length = static_cast(LenStr(text)); + + int64_t the_start = start; + if (the_start < 0) { + the_start += length; + if (the_start < 0) { + the_start = 0; + } + } + + if (the_start > length) { + return -1; + } + + const size_t start_offset = OffsetOf(text, static_cast(the_start)); + const size_t offset = text.find(sub, start_offset); + if (offset == std::wstring::npos) { + return -1; + } + + return the_start + static_cast( + CountCharacters(text, start_offset, offset) + ); +} + +} // namespace common +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/constants.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/constants.cpp new file mode 100644 index 000000000..14781805a --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/constants.cpp @@ -0,0 +1,25 @@ +#include "dummy/constants.hpp" + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +namespace dummy { +namespace constants { + +std::size_t HashBytes::operator()( + const std::vector& bytes +) const { + std::size_t result = 0; + const std::size_t prime = 31; + const std::size_t size = bytes.size(); + for (std::size_t i = 0; i < size; ++i) { + result = bytes[i] + (result * prime); + } + return result; +} + +} // namespace constants +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/iteration.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/iteration.cpp new file mode 100644 index 000000000..99cefa752 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/iteration.cpp @@ -0,0 +1,951 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/iteration.hpp" + +namespace dummy { +namespace iteration { + +// region Pathing + +/** + * Translate the enumeration literal \p property to text. + * + * \param property to be converted into text + * \return text representation of \p property + * \throw std::invalid_argument if \p property invalid + */ +std::wstring PropertyToWstring( + Property property +) { + switch (property) { + case Property::kContainer: + return L"container"; + case Property::kItems: + return L"items"; + case Property::kLevel: + return L"level"; + case Property::kName: + return L"name"; + case Property::kNickname: + return L"nickname"; + case Property::kSomeItems: + return L"some_items"; + case Property::kSpecialItems: + return L"special_items"; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected property literal: ", + std::to_string( + static_cast(property) + ) + ) + ); + } +} // function to_wstring + +// region struct PropertySegment + +PropertySegment::PropertySegment(Property a_property) { + property = a_property; +} + +std::wstring PropertySegment::ToWstring() const { + return common::Concat( + L".", + PropertyToWstring(property) + ); +} + +std::unique_ptr PropertySegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct PropertySegment + +// region struct IndexSegment + +IndexSegment::IndexSegment(size_t an_index) { + index = an_index; +} + +std::wstring IndexSegment::ToWstring() const { + return common::Concat( + L"[", + std::to_wstring(index), + L"]" + ); +} + +std::unique_ptr IndexSegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct IndexSegment + +// region struct KeySegment + +KeySegment::KeySegment(std::wstring a_key) : + key(std::move(a_key)) { + // Intentionally empty. +} + +std::wstring KeySegment::ToWstring() const { + // NOTE (mristin): + // We escape the key the same way a JSON string is escaped so that + // the resulting path reads as a valid C++ expression on + // a ``nlohmann::json`` value, *e.g.*, ``.some_property["some key"]``. + + std::wstring escaped; + escaped.reserve(key.size()); + + for (const wchar_t character : key) { + switch (character) { + case L'\\': + escaped.append(L"\\\\"); + break; + case L'"': + escaped.append(L"\\\""); + break; + case L'\b': + escaped.append(L"\\b"); + break; + case L'\f': + escaped.append(L"\\f"); + break; + case L'\n': + escaped.append(L"\\n"); + break; + case L'\r': + escaped.append(L"\\r"); + break; + case L'\t': + escaped.append(L"\\t"); + break; + default: + escaped.push_back(character); + break; + } + } + + return common::Concat( + L"[\"", + escaped, + L"\"]" + ); +} + +std::unique_ptr KeySegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct KeySegment + +// region struct Path + +Path::Path() { + // Intentionally empty. +} + +Path::Path(const Path& other) { + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } +} + +Path::Path(Path&& other) { + segments = std::move(other.segments); +} + +Path& Path::operator=(const Path& other) { + segments.clear(); + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } + return *this; +} + +Path& Path::operator=(Path&& other) { + if (this != &other) { + segments = std::move(other.segments); + } + return *this; +} + +std::wstring Path::ToWstring() const { + std::vector parts; + parts.reserve(segments.size()); + + for (const std::unique_ptr& segment : segments ) { + parts.emplace_back(segment->ToWstring()); + } + + size_t size = 0; + for (const std::wstring& part : parts) { + size += part.size(); + } + + std::wstring result; + result.reserve(size); + for (const std::wstring& part : parts) { + result.append(part); + } + + return result; +} + +// endregion struct Path + +// endregion Pathing + +namespace { + +// region Iteration over the instances + +/** + * Iterate over no instances at all. + */ +class EmptyIterator : public impl::IIterator { + public: + void Start() override { + // Intentionally empty. + } + + void Next() override { + throw std::logic_error( + "You want to move an EmptyIterator, but it is always done." + ); + } + + bool Done() const override { + return true; + } + + const std::shared_ptr& Get() const override { + throw std::logic_error( + "You want to get an instance from an EmptyIterator, but it is always done." + ); + } + + void AppendToPath(Path&) const override { + throw std::logic_error( + "You want to append the path of an EmptyIterator, but it is always done." + ); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } +}; // class EmptyIterator + +std::unique_ptr Empty() { + return common::make_unique(); +} + +/** + * \brief Iterate over the instances of the children, one child after another. + * + * A child is started only once the previous child is done. + */ +class ChainIterator : public impl::IIterator { + public: + explicit ChainIterator( + std::vector > children + ) : + children_(std::move(children)), + active_(0) { + // Intentionally empty. + } + + ChainIterator(const ChainIterator& other) : + active_(other.active_) { + children_.reserve(other.children_.size()); + for (const std::unique_ptr& child : other.children_) { + children_.emplace_back(child->Clone()); + } + } + + void Start() override { + active_ = 0; + if (!children_.empty()) { + children_[0]->Start(); + } + SkipDoneChildren(); + } + + void Next() override { + children_[active_]->Next(); + SkipDoneChildren(); + } + + bool Done() const override { + return active_ >= children_.size(); + } + + const std::shared_ptr& Get() const override { + return children_[active_]->Get(); + } + + void AppendToPath(Path& path) const override { + children_[active_]->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + std::vector > children_; + + /** + * Index of the child we currently iterate over + */ + std::size_t active_; + + /** + * Move on to the next children, and start them, until one is not done. + */ + void SkipDoneChildren() { + while (active_ < children_.size() && children_[active_]->Done()) { + ++active_; + if (active_ < children_.size()) { + children_[active_]->Start(); + } + } + } +}; // class ChainIterator + +void CollectChildren( + std::vector >& +) { + // Intentionally empty, as there are no more children to collect. +} + +template +void CollectChildren( + std::vector >& children, + std::unique_ptr first, + Rest... rest +) { + children.emplace_back(std::move(first)); + CollectChildren(children, std::move(rest)...); +} + +// NOTE (mristin): +// We can not use an initializer list here, as we can not move the unique pointers +// out of it. +template +std::unique_ptr Chain( + Children... children +) { + std::vector > collected; + collected.reserve(sizeof...(Children)); + CollectChildren(collected, std::move(children)...); + + return common::make_unique(std::move(collected)); +} + +/** + * Iterate over the instances of the \p child, which lives in a property. + */ +class InPropertyIterator : public impl::IIterator { + public: + InPropertyIterator( + Property property, + std::unique_ptr child + ) : + property_(property), + child_(std::move(child)) { + // Intentionally empty. + } + + InPropertyIterator(const InPropertyIterator& other) : + property_(other.property_), + child_(other.child_->Clone()) { + // Intentionally empty. + } + + void Start() override { + child_->Start(); + } + + void Next() override { + child_->Next(); + } + + bool Done() const override { + return child_->Done(); + } + + const std::shared_ptr& Get() const override { + return child_->Get(); + } + + void AppendToPath(Path& path) const override { + path.segments.emplace_back( + common::make_unique(property_) + ); + child_->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + Property property_; + std::unique_ptr child_; +}; // class InPropertyIterator + +std::unique_ptr InProperty( + Property property, + std::unique_ptr child +) { + return common::make_unique(property, std::move(child)); +} + +/** + * \brief Iterate over the instances of every item of a list, one item after another. + * + * The iterator over an item is built only once the iteration reaches the item. + */ +template +class EachIterator : public impl::IIterator { + public: + /** + * Build the iterator over the instances of an item + */ + typedef std::unique_ptr (*OverItem)( + const T& item, + bool recursive + ); + + EachIterator( + const std::vector* items, + OverItem over_item, + bool recursive + ) : + items_(items), + over_item_(over_item), + recursive_(recursive), + index_(0) { + // Intentionally empty. + } + + EachIterator(const EachIterator& other) : + items_(other.items_), + over_item_(other.over_item_), + recursive_(other.recursive_), + index_(other.index_), + item_(other.item_ == nullptr ? nullptr : other.item_->Clone()) { + // Intentionally empty. + } + + void Start() override { + index_ = 0; + item_ = nullptr; + SkipDoneItems(); + } + + void Next() override { + item_->Next(); + SkipDoneItems(); + } + + bool Done() const override { + return index_ >= items_->size(); + } + + const std::shared_ptr& Get() const override { + return item_->Get(); + } + + void AppendToPath(Path& path) const override { + path.segments.emplace_back( + common::make_unique(index_) + ); + item_->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique >(*this); + } + + private: + const std::vector* items_; + OverItem over_item_; + bool recursive_; + + /** + * Index of the item we currently iterate over + */ + std::size_t index_; + + /** + * Iterator over the current item, built once we reached the item + */ + std::unique_ptr item_; + + /** + * Move on to the next items, and build their iterators, until one is not done. + */ + void SkipDoneItems() { + while (index_ < items_->size()) { + if (item_ == nullptr) { + item_ = over_item_((*items_)[index_], recursive_); + item_->Start(); + } + + if (!item_->Done()) { + return; + } + + item_ = nullptr; + ++index_; + } + } +}; // class EachIterator + +template +std::unique_ptr Each( + const std::vector& items, + std::unique_ptr (*over_item)(const T& item, bool recursive), + bool recursive +) { + return common::make_unique >(&items, over_item, recursive); +} + +/** + * \brief Iterate over a single instance. + * + * We keep a copy of the shared pointer, upcast to types::IClass, so that + * \ref Get can return a reference to it. + */ +class OneIterator : public impl::IIterator { + public: + explicit OneIterator( + std::shared_ptr instance + ) : + instance_(std::move(instance)), + done_(true) { + // Intentionally empty. + } + + void Start() override { + done_ = false; + } + + void Next() override { + done_ = true; + } + + bool Done() const override { + return done_; + } + + const std::shared_ptr& Get() const override { + return instance_; + } + + void AppendToPath(Path&) const override { + // Intentionally empty, as the instance itself is the end of the path. + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + std::shared_ptr instance_; + bool done_; +}; // class OneIterator + +std::unique_ptr One( + std::shared_ptr instance +) { + return common::make_unique(std::move(instance)); +} + +/** + * \brief Iterate over the instances referenced from an instance, dispatched on + * its runtime type. + * + * Defined below, once all the classes have been covered. + */ +std::unique_ptr DispatchOnModelType( + const types::IClass& instance, + bool recursive +); + +/** + * \brief Iterate recursively over the instances referenced from an instance. + * + * We dispatch on the runtime type of the instance only in \ref Start so that + * we descend into the instance only once the iteration reaches it. + */ +class DispatchingIterator : public impl::IIterator { + public: + explicit DispatchingIterator( + const types::IClass* instance + ) : + instance_(instance) { + // Intentionally empty. + } + + DispatchingIterator(const DispatchingIterator& other) : + instance_(other.instance_), + child_(other.child_ == nullptr ? nullptr : other.child_->Clone()) { + // Intentionally empty. + } + + void Start() override { + child_ = DispatchOnModelType(*instance_, true); + child_->Start(); + } + + void Next() override { + child_->Next(); + } + + bool Done() const override { + return child_->Done(); + } + + const std::shared_ptr& Get() const override { + return child_->Get(); + } + + void AppendToPath(Path& path) const override { + child_->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + const types::IClass* instance_; + std::unique_ptr child_; +}; // class DispatchingIterator + +/** + * Iterate over the instances referenced from the \p instance, if \p recursive. + */ +std::unique_ptr Over( + const types::IClass& instance, + bool recursive +) { + if (!recursive) { + // NOTE (mristin): + // In the non-recursive mode, we iterate only over the instances referenced + // directly, but not over the instances which they reference in turn. + return Empty(); + } + + return common::make_unique(&instance); +} + +/** + * Iterate over the \p instance, and then over the instances that it references. + */ +template +std::unique_ptr OneThenOver( + const std::shared_ptr& instance, + bool recursive +) { + return Chain(One(instance), Over(*instance, recursive)); +} + +using listOf_Item = std::vector< + std::shared_ptr +>; + +using listOf_SomeItem = std::vector< + std::shared_ptr +>; + +using listOf_SpecialItem = std::vector< + std::shared_ptr +>; + +std::unique_ptr Over_listOf_Item( + const listOf_Item& value, + bool recursive +) { + return Each(value, &OneThenOver, recursive); +} + +std::unique_ptr Over_listOf_SomeItem( + const listOf_SomeItem& value, + bool recursive +) { + return Each(value, &OneThenOver, recursive); +} + +std::unique_ptr Over_listOf_SpecialItem( + const listOf_SpecialItem& value, + bool recursive +) { + return Each(value, &OneThenOver, recursive); +} + +std::unique_ptr Over_Container( + const types::IContainer& that, + bool recursive +) { + return Chain( + InProperty( + Property::kItems, + Over_listOf_Item(that.items(), recursive) + ), + InProperty( + Property::kSomeItems, + Over_listOf_SomeItem(that.some_items(), recursive) + ), + InProperty( + Property::kSpecialItems, + that.special_items().has_value() + ? Over_listOf_SpecialItem((*that.special_items()), recursive) + : Empty() + ) + ); +} + +std::unique_ptr Over_Wrapper( + const types::IWrapper& that, + bool recursive +) { + return InProperty( + Property::kContainer, + OneThenOver(that.container(), recursive) + ); +} + +/** + * \brief Iterate over the instances referenced from the \p instance, + * dispatched on its runtime type. + * + * If \p recursive, we iterate also over the instances which the referenced + * instances reference in turn. + */ +std::unique_ptr DispatchOnModelType( + const types::IClass& instance, + bool recursive +) { + switch (instance.model_type()) { + case types::ModelType::kContainer: + return Over_Container( + dynamic_cast(instance), + recursive + ); + case types::ModelType::kWrapper: + return Over_Wrapper( + dynamic_cast(instance), + recursive + ); + default: + // NOTE (mristin): + // The instances of the other classes reference no other instances. + return Empty(); + } +} + +// endregion Iteration over the instances + +} // namespace + +// region Iterator facade + +Iterator::Iterator( + const Iterator& other +) : + implementation_(other.implementation_->Clone()), + index_(other.index_) { + // Intentionally empty. +} + +Iterator::Iterator( + Iterator&& other +) : + implementation_(std::move(other.implementation_)), + index_(other.index_) { + // Intentionally empty. +} + +Iterator& Iterator::operator=(const Iterator& other) { + return *this = Iterator(other); +} + +Iterator& Iterator::operator=(Iterator&& other) { + if (this != &other) { + implementation_ = std::move(other.implementation_); + index_ = other.index_; + } + + return *this; +} + +const std::shared_ptr& Iterator::operator*() const { + if (implementation_->Done()) { + throw std::logic_error( + "You want to dereference a completed iterator." + ); + } + + return implementation_->Get(); +} + +const std::shared_ptr* Iterator::operator->() { + if (implementation_->Done()) { + throw std::logic_error( + "You want to dereference a completed iterator." + ); + } + + return &(implementation_->Get()); +} + +// Prefix increment +Iterator& Iterator::operator++() { + if (implementation_->Done()) { + throw std::logic_error( + "You want to move a completed iterator." + ); + } + + implementation_->Next(); + index_ = implementation_->Done() ? -1 : index_ + 1; + return *this; +} + +// Postfix increment +Iterator Iterator::operator++(int) { + Iterator result(*this); + ++(*this); + return result; +} + +bool operator==(const Iterator& a, const Iterator& b) { + return a.index_ == b.index_; +} + +bool operator!=(const Iterator& a, const Iterator& b) { + return a.index_ != b.index_; +} + +Path MaterializePath(const Iterator& iterator) { + if (iterator.implementation_->Done()) { + throw std::logic_error( + "You want to materialize path of a completed iterator." + ); + } + + Path path; + iterator.implementation_->AppendToPath(path); + return path; +} + +void PrependToPath(const Iterator& iterator, Path* path) { + if (iterator.implementation_->Done()) { + throw std::logic_error( + "You want to prepend a path of a completed iterator." + ); + } + + Path prefix; + iterator.implementation_->AppendToPath(prefix); + + for ( + auto it = prefix.segments.rbegin(); + it != prefix.segments.rend(); + ++it + ) { + path->segments.emplace_front(std::move(*it)); + } +} + +// endregion Iterator facade + +// region Descents + +// region Descent + +// NOTE (mristin): +// We have to make a copy of the pointer since we would lose otherwise +// in range-based `for` loops, +// see: https://stackoverflow.com/questions/29990045/temporary-lifetime-in-range-for-expression +Descent::Descent( + std::shared_ptr instance +) : instance_(std::move(instance)) { + // Intentionally empty. +} + +Iterator Descent::begin() const { + std::unique_ptr it_impl( + DispatchOnModelType(*instance_, true) + ); + + it_impl->Start(); + + // NOTE(mristin): + // We short-circuit here for memory frugality, + // as we can immediately dispose it_impl. + if (it_impl->Done()) { + return end(); + } + + return Iterator(std::move(it_impl)); +} + +const Iterator& Descent::end() const { + static Iterator iterator(Empty()); + return iterator; +} + +// endregion Descent + +// region DescentOnce + +// NOTE (mristin): +// We have to make a copy of the pointer since we would lose otherwise +// in range-based `for` loops, +// see: https://stackoverflow.com/questions/29990045/temporary-lifetime-in-range-for-expression +DescentOnce::DescentOnce( + std::shared_ptr instance +) : instance_(std::move(instance)) { + // Intentionally empty. +} + +Iterator DescentOnce::begin() const { + std::unique_ptr it_impl( + DispatchOnModelType(*instance_, false) + ); + + it_impl->Start(); + + // NOTE(mristin): + // We short-circuit here for memory frugality, + // as we can immediately dispose it_impl. + if (it_impl->Done()) { + return end(); + } + + return Iterator(std::move(it_impl)); +} + +const Iterator& DescentOnce::end() const { + static Iterator iterator(Empty()); + return iterator; +} + +// endregion DescentOnce + +// endregion Descents + +} // namespace iteration +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/jsonization.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/jsonization.cpp new file mode 100644 index 000000000..6fec18c96 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/jsonization.cpp @@ -0,0 +1,2917 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/jsonization.hpp" +#include "dummy/stringification.hpp" +#include "dummy/wstringification.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { +namespace jsonization { + +// region JSON path helpers + +/** + * \brief Escape \p text as the content of a JSON string. + * + * The control characters without a short escape are written as Unicode + * escapes, as JSON.stringify does. + */ +std::wstring EscapeForJsonString( + const std::wstring& text +) { + static const wchar_t* const kHexDigits = L"0123456789abcdef"; + + std::wstring escaped; + escaped.reserve(text.size()); + for (const wchar_t character : text) { + switch (character) { + case L'\\': + escaped.append(L"\\\\"); + break; + case L'"': + escaped.append(L"\\\""); + break; + case L'\b': + escaped.append(L"\\b"); + break; + case L'\f': + escaped.append(L"\\f"); + break; + case L'\n': + escaped.append(L"\\n"); + break; + case L'\r': + escaped.append(L"\\r"); + break; + case L'\t': + escaped.append(L"\\t"); + break; + default: + if (static_cast(character) < 0x20) { + escaped.append(L"\\u00"); + escaped.push_back(kHexDigits[(character >> 4) & 0xF]); + escaped.push_back(kHexDigits[character & 0xF]); + } else { + escaped.push_back(character); + } + break; + } + } + + return escaped; +} + +/** + * \brief Check whether a JSON path writes the property \p name after a dot. + * + * The other names are written in brackets as JSON strings, + * e.g., ["$eq"]. + */ +bool IsVariableName( + const std::wstring& name +) { + if (name.empty()) { + return false; + } + + for (size_t i = 0; i < name.size(); ++i) { + const wchar_t character = name[i]; + + const bool is_letter_or_underscore = ( + (character >= L'a' && character <= L'z') + || (character >= L'A' && character <= L'Z') + || character == L'_' + ); + + const bool is_digit = character >= L'0' && character <= L'9'; + + if (!(is_letter_or_underscore || (i > 0 && is_digit))) { + return false; + } + } + + return true; +} + +// endregion JSON path helpers + +// region PropertySegment + +PropertySegment::PropertySegment( + std::wstring a_name +) : + name(std::move(a_name)) { + // Intentionally empty. +} + +std::wstring PropertySegment::ToWstring() const { + if (IsVariableName(name)) { + return common::Concat( + L".", + name + ); + } + + return common::Concat( + L"[\"", + EscapeForJsonString(name), + L"\"]" + ); +} + +std::unique_ptr PropertySegment::Clone() const { + return common::make_unique(*this); +} + +// endregion PropertySegment + +// region IndexSegment + +IndexSegment::IndexSegment( + size_t an_index +) : + index(an_index) { + // Intentionally empty. +} + +std::wstring IndexSegment::ToWstring() const { + return common::Concat( + L"[", + std::to_wstring(index), + L"]" + ); +} + +std::unique_ptr IndexSegment::Clone() const { + return common::make_unique(*this); +} + +// endregion IndexSegment + +// region KeySegment + +KeySegment::KeySegment( + std::wstring a_key +) : + key(std::move(a_key)) { + // Intentionally empty. +} + +std::wstring KeySegment::ToWstring() const { + // NOTE (mristin): + // A key can be any string at all, so we always write it as a JSON string in + // brackets, as in the other SDKs. + return common::Concat( + L"[\"", + EscapeForJsonString(key), + L"\"]" + ); +} + +std::unique_ptr KeySegment::Clone() const { + return common::make_unique(*this); +} + +// endregion KeySegment + +// region struct Path + +Path::Path() { + // Intentionally empty. +} + +Path::Path(const Path& other) { + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } +} + +Path::Path(Path&& other) { + segments = std::move(other.segments); +} + +Path& Path::operator=(const Path& other) { + segments.clear(); + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } + return *this; +} + +Path& Path::operator=(Path&& other) { + if (this != &other) { + segments = std::move(other.segments); + } + return *this; +} + +std::wstring Path::ToWstring() const { + if (segments.empty()) { + return L""; + } + + std::deque parts; + for (const std::unique_ptr& segment : segments ) { + parts.emplace_back(segment->ToWstring()); + } + + // NOTE (mristin): + // The path starts with the name of the first property without a dot, as in + // the other SDKs, *e.g.*, ``items[0].name``. + const PropertySegment* first_property( + dynamic_cast(segments.front().get()) + ); + if (first_property != nullptr && IsVariableName(first_property->name)) { + parts.front() = first_property->name; + } + + size_t out_len = 0; + for (const std::wstring& part : parts) { + out_len += part.size(); + } + + std::wstring out; + out.reserve(out_len); + for (const std::wstring& part : parts) { + out.append(part); + } + + return out; +} + +// endregion struct Path + +// region De-serialization + +// region class DeserializationError + +DeserializationError::DeserializationError( + std::wstring a_cause +) : + cause(a_cause) { + // Intentionally empty. +} + +DeserializationError::DeserializationError( + std::wstring a_cause, + Path a_path +) : + cause(a_cause), + path(a_path) { + // Intentionally empty. +} + +// endregion class DeserializationError + +std::pair< + common::optional, + common::optional +> DeserializeBool( + const nlohmann::json& json +) { + if (!json.is_boolean()) { + std::wstring message = common::Concat( + L"Expected a boolean, but got a value of type: ", + common::Utf8ToWstring(json.type_name()) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + json.get(), + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> DeserializeInt64( + const nlohmann::json& json +) { + if (!json.is_number()) { + std::wstring message = common::Concat( + L"Expected an integer number, but got a value of type: ", + common::Utf8ToWstring(json.type_name()) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + static_assert( + std::is_same::value, + "Expected nlohmann::json::number_integer_t to equal int64_t, " + "but it does not." + ); + + if (json.is_number_integer()) { + return std::make_pair< + common::optional, + common::optional + >( + json.get(), + common::nullopt + ); + } + + if (json.is_number_unsigned()) { + std::wstring message = common::Concat( + L"Expected a 64-bit integer number, " + L"but got an unsigned integer number which does not fit in that range: ", + std::to_wstring(json.get()) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + // NOTE (mristin): + // We have to check that the number is an integer even though it can + // not be stored in int64_t in order to give an informative message. + + const nlohmann::json::number_float_t number( + json.get() + ); + + nlohmann::json::number_float_t integer_part; + const bool is_integer( + std::modf(number, &integer_part) == 0 + ); + if (is_integer) { + std::wstring message = common::Concat( + L"Expected a 64-bit integer number, " + L"but got an integer number which does not fit in that range: ", + std::to_wstring(number) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } else { + std::wstring message = common::Concat( + L"Expected a 64-bit integer number, " + L"but got a non-integer number: ", + std::to_wstring(number) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } +} + +std::pair< + common::optional, + common::optional +> DeserializeDouble( + const nlohmann::json& json +) { + if (!json.is_number()) { + std::wstring message = common::Concat( + L"Expected a number, but got a value of type: ", + common::Utf8ToWstring(json.type_name()) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + static_assert( + std::is_same::value, + "Expected nlohmann::json::number_float_t to equal double, " + "but it does not." + ); + + const double value(json.get()); + + // NOTE (mristin): + // JSON knows neither an infinity nor a not-a-number, so a conformant parser + // can never give us one. The caller can still hand us a JSON value which + // has been constructed programmatically, so we have to check here. + + if (!std::isfinite(value)) { + std::wstring message = common::Concat( + L"Expected a finite number, but got: ", + std::to_wstring(value) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + value, + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> DeserializeWstring( + const nlohmann::json& json +) { + if (!json.is_string()) { + std::wstring message = common::Concat( + L"Expected a string, but got a value of type: ", + common::Utf8ToWstring(json.type_name()) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + common::Utf8ToWstring(*(json.get_ptr())), + common::nullopt + ); +} + +std::pair< + common::optional >, + common::optional +> DeserializeByteArray( + const nlohmann::json& json +) { + if (!json.is_string()) { + std::wstring message = common::Concat( + L"Expected a base64-encoded byte array as a string, " + L"but got a value of type: ", + common::Utf8ToWstring(json.type_name()) + );; + + return std::make_pair< + common::optional >, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + common::expected< + std::vector, + std::string + > bytes = stringification::Base64Decode( + *(json.get_ptr()) + ); + + if (!bytes.has_value()) { + std::wstring message = common::Concat( + L"Failed to base64-decode the bytes from a string: ", + common::Utf8ToWstring(bytes.error()) + ); + + return std::make_pair< + common::optional >, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional >, + common::optional + >( + std::move(*bytes), + common::nullopt + ); +} + +/** + * Get the property `modelType` from the JSON value expected as a JSON object. + */ +std::pair< + const std::string*, + common::optional +> GetModelTypeFrom( + const nlohmann::json& json +) { + if (!json.is_object()) { + std::wstring message = common::Concat( + L"Expected an object, but got: ", + common::Utf8ToWstring(json.type_name()) + ); + + return std::make_pair< + const std::string*, + common::optional + >( + nullptr, + common::make_optional( + message + ) + ); + } + + if (!json.contains("modelType")) { + return std::make_pair< + const std::string*, + common::optional + >( + nullptr, + common::make_optional( + L"The required property modelType is missing" + ) + ); + } + + const nlohmann::json& model_type_prop = json["modelType"]; + if (!model_type_prop.is_string()) { + std::wstring message = common::Concat( + L"Expected modelType to be a string, but got: ", + common::Utf8ToWstring(model_type_prop.type_name()) + ); + + common::optional error( + common::make_optional( + message + ) + ); + error->path.segments.emplace_front( + common::make_unique( + L"modelType" + ) + ); + + // NOTE (mristin): + // We have to explicitly use the constructor instead of std::make_pair + // as `const std::string*` can not be automatically converted to a rvalue. + return std::pair< + const std::string*, + common::optional + >( + nullptr, + error + ); + } + + static_assert( + std::is_same::value, + "Expected nlohmann::json::string_t to equal std::string, but it does not." + ); + + const std::string* model_type( + model_type_prop.get_ptr() + ); + + // NOTE (mristin): + // We have to explicitly use the constructor instead of std::make_pair + // as `const std::string*` can not be automatically converted to a rvalue. + return std::pair< + const std::string*, + common::optional + >( + model_type, + common::nullopt + ); +} + +/** + * \brief Give out a failed de-serialization with \p cause as its message. + * + * \tparam T type of the value which could not be de-serialized + * \param cause human-readable description of the failure + * \return no value, and the error + */ +template +std::pair< + common::optional, + common::optional +> NoInstanceAndDeserializationErrorWithCause( + std::wstring cause +) { + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + std::move(cause) + ) + ); +} + +/** + * \brief Give out a failed de-serialization with \p error. + * + * \tparam T type of the value which could not be de-serialized + * \param error of the de-serialization + * \return no value, and the error + */ +template +std::pair< + common::optional, + common::optional +> NoInstanceAndDeserializationError( + DeserializationError error +) { + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + std::move(error) + ) + ); +} + +/** + * Map JSON \c modelType strings to model types. + */ +const std::unordered_map< + std::string, + types::ModelType +> kModelTypeStringToModelType = { + { + "SomeItem", + types::ModelType::kSomeItem + }, + { + "SpecialItem", + types::ModelType::kSpecialItem + }, + { + "Container", + types::ModelType::kContainer + }, + { + "Wrapper", + types::ModelType::kWrapper + } +}; + +common::optional ModelTypeFromModelTypeString( + const std::string& model_type_str +) { + auto it = kModelTypeStringToModelType.find(model_type_str); + if (it == kModelTypeStringToModelType.end()) { + return common::nullopt; + } + + return it->second; +} + +/** + * \brief De-serialize a list of items from \p json. + * + * \param json value expected to be an array + * \param deserialize_item de-serializes an item + * \return the list, or an error, if any + */ +template +std::pair< + common::optional >, + common::optional +> DeserializeList( + const nlohmann::json& json, + DeserializeItemT&& deserialize_item +) { + if (!json.is_array()) { + std::wstring message = common::Concat( + L"Expected an array, but got: ", + common::Utf8ToWstring( + json.type_name() + ) + ); + + return std::make_pair< + common::optional >, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + common::optional > list( + common::make_optional >() + ); + + list->reserve(json.size()); + + size_t index = 0; + + for(const nlohmann::json& item : json) { + common::optional deserialized; + common::optional error; + + std::tie(deserialized, error) = deserialize_item(item); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique( + index + ) + ); + + return std::make_pair< + common::optional >, + common::optional + >( + common::nullopt, + std::move(error) + ); + } + + list->emplace_back(std::move(*deserialized)); + + ++index; + } + + return std::make_pair( + list, + common::nullopt + ); +} + +/** + * \brief De-serialize a list of instances from \p json. + * + * \param json value expected to be an array + * \param additional_properties handed over to \p deserialize_item + * \param deserialize_item de-serializes an item + * \return the list, or an error, if any + */ +template +std::pair< + common::optional >, + common::optional +> DeserializeList( + const nlohmann::json& json, + bool additional_properties, + DeserializeItemT&& deserialize_item +) { + return DeserializeList( + json, + [&additional_properties, &deserialize_item]( + const nlohmann::json& item + ) { + return deserialize_item(item, additional_properties); + } + ); +} + +namespace properties { + +enum class OfSomeItem : std::uint32_t { + kName, + kNickname, + kModelType +}; // enum class OfSomeItem + +enum class OfSpecialItem : std::uint32_t { + kName, + kNickname, + kLevel, + kModelType +}; // enum class OfSpecialItem + +enum class OfContainer : std::uint32_t { + kItems, + kSomeItems, + kSpecialItems +}; // enum class OfContainer + +enum class OfWrapper : std::uint32_t { + kContainer +}; // enum class OfWrapper + +const std::unordered_map< + std::string, + OfSomeItem +> kMapOfSomeItem = { + { + "name", + OfSomeItem::kName + }, + { + "nickname", + OfSomeItem::kNickname + }, + { + "modelType", + OfSomeItem::kModelType + } +}; + +const std::unordered_map< + std::string, + OfSpecialItem +> kMapOfSpecialItem = { + { + "name", + OfSpecialItem::kName + }, + { + "nickname", + OfSpecialItem::kNickname + }, + { + "level", + OfSpecialItem::kLevel + }, + { + "modelType", + OfSpecialItem::kModelType + } +}; + +const std::unordered_map< + std::string, + OfContainer +> kMapOfContainer = { + { + "items", + OfContainer::kItems + }, + { + "someItems", + OfContainer::kSomeItems + }, + { + "specialItems", + OfContainer::kSpecialItems + } +}; + +const std::unordered_map< + std::string, + OfWrapper +> kMapOfWrapper = { + { + "container", + OfWrapper::kContainer + } +}; + +} // namespace properties + +/** + * \brief Create the exception to be thrown on an unexpected property literal. + * + * Every ``switch`` over the properties of a class covers all the literals of + * its enumeration, so we can only get here if the value has been corrupted. + * We report that as a logic error, and not as a de-serialization error, since + * it does not originate in the input. + * + * \param enum_name name of the property enumeration, for the message + * \param property the unexpected literal + * \return the exception to be thrown + */ +template +std::logic_error UnexpectedPropertyLiteralError( + const char* enum_name, + EnumT property +) { + return std::logic_error( + common::Concat( + "Unexpected properties literal of ", + enum_name, + ": ", + std::to_string( + static_cast(property) + ) + ) + ); +} + +/** + * \brief Parse the properties of an instance from \p json. + * + * This function factors out everything which the property loop of a class does + * not say about the class it belongs to: walking the keys, looking the property + * up, refusing or accepting an unknown one and marking the property on + * the error path. The class itself supplies only \p on_property, which + * dispatches on the property and assigns the local variables that its + * constructor is finally called with. + * + * NOTE (mristin): + * We walk the keys which are actually there, instead of asking for each + * property of the class whether it is there. An instance carries only a few of + * the many properties which a class declares, and nlohmann's object is + * a ``std::map``, so asking costs a tree walk per property -- twice over, as + * the value then has to be fetched. Walking also detects an unknown key on + * the way, which is why there is no separate pass for that. + * + * NOTE (mristin): + * We take \p map_of_properties in, instead of letting \p on_property work on + * the JSON name of the property, because we want the class to dispatch with + * a hard-wired ``switch`` whose branches assign the local variables of + * the caller. + * + * A ``switch`` needs an integral constant, and C++ can not switch on a string, + * so the name has to be translated into a literal of the property enumeration + * first. We do that here rather than in the class so that the translation, and + * the error reported when the name matches no property at all, are written + * once instead of once per class. + * + * \param json object whose properties are to be parsed + * \param map_of_properties maps the JSON name of a property to its literal + * \param additional_properties if not set, refuse a key which matches + * no property + * \param on_property parses the value of the recognized property + * \return the error, if the parsing failed + */ +template +common::optional ParseProperties( + const nlohmann::json& json, + const std::unordered_map& map_of_properties, + bool additional_properties, + const OnPropertyT& on_property +) { + #ifdef DEBUG + if (!json.is_object()) { + throw std::logic_error( + "Unexpected non-object in ParseProperties. " + "ParseProperties expects the caller to have checked that." + ); + } + #endif + + for (const auto& key_val : json.items()) { + auto it( + map_of_properties.find(key_val.key()) + ); + + if (it == map_of_properties.end()) { + if (additional_properties) { + continue; + } + + return DeserializationError( + common::Concat( + L"Unexpected additional property: ", + common::Utf8ToWstring(key_val.key()) + ) + ); + } + + common::optional error( + on_property(it->second, key_val.value()) + ); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique( + common::Utf8ToWstring(key_val.key()) + ) + ); + + return error; + } + } + + return common::nullopt; +} + +/** + * \brief Check that \p json is a JSON object. + * + * \param json value to be checked + * \return the error, if \p json is anything else + */ +common::optional CheckJsonObject( + const nlohmann::json& json +) { + if (!json.is_object()) { + return DeserializationError( + common::Concat( + L"Expected an object, but got: ", + common::Utf8ToWstring(json.type_name()) + ) + ); + } + + return common::nullopt; +} + +/** + * \brief Check that \p json is an object whose model type is \p expected. + * + * The model type is compared as the string which came on the wire. That refuses + * a value of the wrong type just as well as parsing it would, and costs neither + * a conversion to a wide string nor the allocation which goes with it. + * + * \param json value expected to be an object carrying a model type + * \param expected model type of the class + * \return the error, if \p json does not bear \p expected + */ +common::optional CheckModelType( + const nlohmann::json& json, + const char* expected +) { + const std::string* model_type; + common::optional error; + + std::tie( + model_type, + error + ) = GetModelTypeFrom(json); + + if (error.has_value()) { + return error; + } + + if (*model_type != expected) { + return DeserializationError( + common::Concat( + L"Expected model type '", + common::Utf8ToWstring(expected), + L"', but got: ", + common::Utf8ToWstring(*model_type) + ) + ); + } + + return common::nullopt; +} + +/** + * \brief Assign the value parsed to \p target, or give out the error of the parse. + * + * We deliberately take the *result* of a parse instead of the JSON value and + * the function which parses it. The item parsers of a tuple vary both in number + * and in type, so no signature taking the parser could serve every parse; + * taking the result lets this single function serve all of them. + * + * \param target variable to be assigned the value parsed + * \param parsed result of the parse + * \return the error, if the parse failed + */ +template +common::optional ParseInto( + common::optional& target, + std::pair< + common::optional, + common::optional + >&& parsed +) { + if (parsed.second.has_value()) { + return std::move(parsed.second); + } + + target = std::move(parsed.first); + + return common::nullopt; +} + +/** + * \brief Dispatch the deserialization for an instance + * of types::IItem. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the deserialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeItem( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Parse the properties of an instance of types::ISomeItem. + * + * The model type, if the class carries one, is expected to have been verified + * by the caller, which is what lets a dispatcher avoid verifying it twice. + * + * \param json object whose properties are to be parsed + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> ParsePropertiesOfSomeItem( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Dispatch the deserialization for an instance + * of types::ISomeItem. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the deserialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeSomeItem( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Parse the properties of an instance of types::ISpecialItem. + * + * The model type, if the class carries one, is expected to have been verified + * by the caller, which is what lets a dispatcher avoid verifying it twice. + * + * \param json object whose properties are to be parsed + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> ParsePropertiesOfSpecialItem( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Deserialize \p json to an instance of types::ISpecialItem. + * + * No dispatch is performed. The model type, if the class carries one, is + * verified here, since the caller has not read it. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> DeserializeSpecialItem( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Parse the properties of an instance of types::IContainer. + * + * The model type, if the class carries one, is expected to have been verified + * by the caller, which is what lets a dispatcher avoid verifying it twice. + * + * \param json object whose properties are to be parsed + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ParsePropertiesOfContainer( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Deserialize \p json to an instance of types::IContainer. + * + * No dispatch is performed. The model type, if the class carries one, is + * verified here, since the caller has not read it. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeContainer( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Parse the properties of an instance of types::IWrapper. + * + * The model type, if the class carries one, is expected to have been verified + * by the caller, which is what lets a dispatcher avoid verifying it twice. + * + * \param json object whose properties are to be parsed + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ParsePropertiesOfWrapper( + const nlohmann::json& json, + bool additional_properties +); + +/** + * \brief Deserialize \p json to an instance of types::IWrapper. + * + * No dispatch is performed. The model type, if the class carries one, is + * verified here, since the caller has not read it. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \return the de-serialized instance, or an error, if any + */ +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeWrapper( + const nlohmann::json& json, + bool additional_properties +); + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeItem( + const nlohmann::json& json, + bool additional_properties +) { + const std::string* model_type_str; + common::optional error; + + std::tie( + model_type_str, + error + ) = GetModelTypeFrom(json); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + common::optional model_type( + ModelTypeFromModelTypeString(*model_type_str) + ); + + if (!model_type.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"The model type does not correspond to any known class: ", + common::Utf8ToWstring(*model_type_str) + ) + ); + } + + switch (*model_type) { + case types::ModelType::kSomeItem: + return ParsePropertiesOfSomeItem< + types::IItem + >(json, additional_properties); + case types::ModelType::kSpecialItem: + return ParsePropertiesOfSpecialItem< + types::IItem + >(json, additional_properties); + default: + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"The dispatch to the JSON de-serialization of " + L"types::IItem " + L"is not defined for model type: ", + common::Utf8ToWstring(*model_type_str) + ) + ); + } +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> ParsePropertiesOfSomeItem( + const nlohmann::json& json, + bool additional_properties +) { + common::optional the_name; + + common::optional the_nickname; + + common::optional error( + ParseProperties( + json, + properties::kMapOfSomeItem, + additional_properties, + [&]( + properties::OfSomeItem property, + const nlohmann::json& value + ) -> common::optional { + switch (property) { + case properties::OfSomeItem::kName: + return ParseInto( + the_name, + DeserializeWstring(value) + ); + case properties::OfSomeItem::kNickname: + return ParseInto( + the_nickname, + DeserializeWstring(value) + ); + case properties::OfSomeItem::kModelType: + // NOTE (mristin): + // The model type has been verified before the loop, so there is nothing + // left to do with it here. + return common::nullopt; + default: + throw UnexpectedPropertyLiteralError( + "properties::OfSomeItem", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + if (!the_name.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property name is missing" + ); + } + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::SomeItem( + std::move(*the_name), + std::move(the_nickname) + ) + ), + common::nullopt + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeSomeItem( + const nlohmann::json& json, + bool additional_properties +) { + const std::string* model_type_str; + common::optional error; + + std::tie( + model_type_str, + error + ) = GetModelTypeFrom(json); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + common::optional model_type( + ModelTypeFromModelTypeString(*model_type_str) + ); + + if (!model_type.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"The model type does not correspond to any known class: ", + common::Utf8ToWstring(*model_type_str) + ) + ); + } + + switch (*model_type) { + case types::ModelType::kSomeItem: + return ParsePropertiesOfSomeItem< + types::ISomeItem + >(json, additional_properties); + case types::ModelType::kSpecialItem: + return ParsePropertiesOfSpecialItem< + types::ISomeItem + >(json, additional_properties); + default: + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"The dispatch to the JSON de-serialization of " + L"types::ISomeItem " + L"is not defined for model type: ", + common::Utf8ToWstring(*model_type_str) + ) + ); + } +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> ParsePropertiesOfSpecialItem( + const nlohmann::json& json, + bool additional_properties +) { + common::optional the_name; + + common::optional the_nickname; + + common::optional the_level; + + common::optional error( + ParseProperties( + json, + properties::kMapOfSpecialItem, + additional_properties, + [&]( + properties::OfSpecialItem property, + const nlohmann::json& value + ) -> common::optional { + switch (property) { + case properties::OfSpecialItem::kName: + return ParseInto( + the_name, + DeserializeWstring(value) + ); + case properties::OfSpecialItem::kNickname: + return ParseInto( + the_nickname, + DeserializeWstring(value) + ); + case properties::OfSpecialItem::kLevel: + return ParseInto( + the_level, + DeserializeInt64(value) + ); + case properties::OfSpecialItem::kModelType: + // NOTE (mristin): + // The model type has been verified before the loop, so there is nothing + // left to do with it here. + return common::nullopt; + default: + throw UnexpectedPropertyLiteralError( + "properties::OfSpecialItem", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + if (!the_name.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property name is missing" + ); + } + + if (!the_level.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property level is missing" + ); + } + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::SpecialItem( + std::move(*the_name), + std::move(*the_level), + std::move(the_nickname) + ) + ), + common::nullopt + ); +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> DeserializeSpecialItem( + const nlohmann::json& json, + bool additional_properties +) { + common::optional error( + CheckModelType( + json, + "SpecialItem" + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + return ParsePropertiesOfSpecialItem( + json, + additional_properties + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ParsePropertiesOfContainer( + const nlohmann::json& json, + bool additional_properties +) { + common::optional< + std::vector< + std::shared_ptr + > + > the_items; + + common::optional< + std::vector< + std::shared_ptr + > + > the_some_items; + + common::optional< + std::vector< + std::shared_ptr + > + > the_special_items; + + common::optional error( + ParseProperties( + json, + properties::kMapOfContainer, + additional_properties, + [&]( + properties::OfContainer property, + const nlohmann::json& value + ) -> common::optional { + switch (property) { + case properties::OfContainer::kItems: + return ParseInto( + the_items, + DeserializeList< + std::shared_ptr + >( + value, + additional_properties, + DeserializeItem + ) + ); + case properties::OfContainer::kSomeItems: + return ParseInto( + the_some_items, + DeserializeList< + std::shared_ptr + >( + value, + additional_properties, + DeserializeSomeItem + ) + ); + case properties::OfContainer::kSpecialItems: + return ParseInto( + the_special_items, + DeserializeList< + std::shared_ptr + >( + value, + additional_properties, + DeserializeSpecialItem< + types::ISpecialItem + > + ) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfContainer", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + if (!the_items.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property items is missing" + ); + } + + if (!the_some_items.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property someItems is missing" + ); + } + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::Container( + std::move(*the_items), + std::move(*the_some_items), + std::move(the_special_items) + ) + ), + common::nullopt + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeContainer( + const nlohmann::json& json, + bool additional_properties +) { + common::optional error( + CheckJsonObject(json) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + return ParsePropertiesOfContainer(json, additional_properties); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ParsePropertiesOfWrapper( + const nlohmann::json& json, + bool additional_properties +) { + common::optional > the_container; + + common::optional error( + ParseProperties( + json, + properties::kMapOfWrapper, + additional_properties, + [&]( + properties::OfWrapper property, + const nlohmann::json& value + ) -> common::optional { + switch (property) { + case properties::OfWrapper::kContainer: + return ParseInto( + the_container, + DeserializeContainer( + value, + additional_properties + ) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfWrapper", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + if (!the_container.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property container is missing" + ); + } + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::Wrapper( + std::move(*the_container) + ) + ), + common::nullopt + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> DeserializeWrapper( + const nlohmann::json& json, + bool additional_properties +) { + common::optional error( + CheckJsonObject(json) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + return ParsePropertiesOfWrapper(json, additional_properties); +} + +/** + * \brief De-serialize \p json and render the outcome as an expected value. + * + * \param json value to be de-serialized + * \param additional_properties if not set, check that \p json contains + * no additional properties + * \param deserialize de-serializes the value + * \return the de-serialized value, or the error + */ +template +common::expected< + T, + DeserializationError +> DeserializeFrom( + const nlohmann::json& json, + bool additional_properties, + const DeserializeT& deserialize +) { + common::optional instance; + common::optional error; + + std::tie( + instance, + error + ) = deserialize(json, additional_properties); + + if (instance.has_value()) { + return std::move(*instance); + } + + if (!error.has_value()) { + throw std::logic_error( + "Unexpected null error when null instance." + ); + } + + return common::make_unexpected( + std::move(*error) + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> ItemFrom( + const nlohmann::json& json, + bool additional_properties +) { + return DeserializeFrom< + std::shared_ptr + >( + json, + additional_properties, + DeserializeItem + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> SomeItemFrom( + const nlohmann::json& json, + bool additional_properties +) { + return DeserializeFrom< + std::shared_ptr + >( + json, + additional_properties, + DeserializeSomeItem + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> SpecialItemFrom( + const nlohmann::json& json, + bool additional_properties +) { + return DeserializeFrom< + std::shared_ptr + >( + json, + additional_properties, + DeserializeSpecialItem< + types::ISpecialItem + > + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> ContainerFrom( + const nlohmann::json& json, + bool additional_properties +) { + return DeserializeFrom< + std::shared_ptr + >( + json, + additional_properties, + DeserializeContainer + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> WrapperFrom( + const nlohmann::json& json, + bool additional_properties +) { + return DeserializeFrom< + std::shared_ptr + >( + json, + additional_properties, + DeserializeWrapper + ); +} + +// endregion De-serialization + +// region Serialization + +/** + * \brief Represent a serialization error. + * + * We use this error internally to avoid unnecessary stack unwinding, + * but throw the \ref SerializationException at the final site of + * the serialization for the user. + */ +struct SerializationError { + /** + * Human-readable description of the error + */ + std::wstring cause; + + /** + * Path to the value that caused the error + */ + iteration::Path path; + + explicit SerializationError( + std::wstring a_cause + ) : cause(std::move(a_cause)) { + // Intentionally empty. + } +}; // struct SerializationError + +// region SerializationException + +std::string RenderSerializationErrorMessage( + const std::wstring& cause, + const iteration::Path& path +) { + return common::WstringToUtf8( + common::Concat( + L"Serialization failed at ", + path.ToWstring(), + L": ", + cause + ) + ); +} + +SerializationException::SerializationException( + std::wstring cause, + iteration::Path path +) : + cause_(std::move(cause)), + path_(std::move(path)), + msg_(RenderSerializationErrorMessage(cause, path)) { + // Intentionally empty. +} + +const char* SerializationException::what() const noexcept { + return msg_.c_str(); +} + +const std::wstring& SerializationException::cause() const noexcept { + return cause_; +} + +const iteration::Path& SerializationException::path() const noexcept { + return path_; +} + +// endregion SerializationException + +/** + * Serialize the given boolean to a JSON value. + */ +nlohmann::json SerializeBool( + bool value +) { + return value; +} + +/** + * \brief Serialize the given number to a JSON value. + * + * We verify that the integer is within the range representable by 64-bit floats + * for interoperability with other de-serializers. + */ +std::pair< + common::optional, + common::optional +> SerializeInt64(int64_t value) { + if ( + value < -9007199254740991L + || value > 9007199254740991L + ) { + const std::wstring message = common::Concat( + L"The integer ", + std::to_wstring(value), + L" can not be serialized to JSON " + L"as it is outside the range [-2^53 + 1, 2^53 - 1] and can not " + L"be exactly represented as a 64-bit floating point number." + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + common::make_optional(value), + common::nullopt + ); +} + +/** + * \brief Serialize the given floating-point number to a JSON value. + * + * JSON knows neither an infinity nor a not-a-number, so we refuse to serialize + * them instead of silently writing them out as ``null``. + */ +std::pair< + common::optional, + common::optional +> SerializeDouble(double value) { + if (!std::isfinite(value)) { + const std::wstring message = common::Concat( + L"The floating-point number ", + std::to_wstring(value), + L" can not be serialized to JSON as JSON knows no infinity " + L"and no not-a-number." + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + message + ) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + common::make_optional(value), + common::nullopt + ); +} + +/** + * Serialize the given text to a JSON value. + */ +nlohmann::json SerializeWstring( + const std::wstring& text +) { + return nlohmann::json( + common::WstringToUtf8(text) + ); +} + +/** + * Serialize the given bytes to a JSON value. + */ +nlohmann::json SerializeByteArray( + const std::vector& bytes +) { + return nlohmann::json( + stringification::Base64Encode(bytes) + ); +} + +/** + * Serialize the given list to a JSON array where item serialization might fail. + */ +template +std::pair< + common::optional, + common::optional +> SerializeListWithFallible( + const std::vector& list, + FallibleSerializeItemT&& fallible_serialize_item +) { + nlohmann::json serialized = nlohmann::json::array(); + + serialized.get_ptr()->reserve( + list.size() + ); + + size_t index = 0; + + for (const T& item : list) { + common::optional json_item; + common::optional error; + + std::tie( + json_item, + error + ) = fallible_serialize_item(item); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique( + index + ) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + std::move(error) + ); + } + + serialized.emplace_back( + std::move(*json_item) + ); + + ++index; + } + + return std::make_pair( + std::move(serialized), + common::nullopt + ); +} + +/** + * Serialize the given list to a JSON array where item serialization can not fail. + */ +template +nlohmann::json SerializeListWithInfallible( + const std::vector& list, + InfallibleSerializeItemT&& infallible_serialize_item +) { + nlohmann::json serialized = nlohmann::json::array(); + + serialized.get_ptr()->reserve( + list.size() + ); + + for (const T& item : list) { + serialized.emplace_back( + infallible_serialize_item(item) + ); + } + + return serialized; +} + +/** + * Serialize the given list of instances to a JSON array where item + * serialization might fail. + * + * The items are pointers, which we dereference for the item serializer. + */ +template +std::pair< + common::optional, + common::optional +> SerializeListOfInstancesWithFallible( + const std::vector >& list, + FallibleSerializeItemT&& fallible_serialize_item +) { + nlohmann::json serialized = nlohmann::json::array(); + + serialized.get_ptr()->reserve( + list.size() + ); + + size_t index = 0; + + for (const std::shared_ptr& item : list) { + common::optional json_item; + common::optional error; + + std::tie( + json_item, + error + ) = fallible_serialize_item(*item); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique( + index + ) + ); + + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + std::move(error) + ); + } + + serialized.emplace_back( + std::move(*json_item) + ); + + ++index; + } + + return std::make_pair( + std::move(serialized), + common::nullopt + ); +} + +/** + * Serialize the given list of instances to a JSON array where item + * serialization can not fail. + * + * The items are pointers, which we dereference for the item serializer. + */ +template +nlohmann::json SerializeListOfInstancesWithInfallible( + const std::vector >& list, + InfallibleSerializeItemT&& infallible_serialize_item +) { + nlohmann::json serialized = nlohmann::json::array(); + + serialized.get_ptr()->reserve( + list.size() + ); + + for (const std::shared_ptr& item : list) { + serialized.emplace_back( + infallible_serialize_item(*item) + ); + } + + return serialized; +} + +/** + * \brief Give out a failed serialization with \p cause as its message. + * + * \param cause human-readable description of the failure + * \return no value, and the error + */ +std::pair< + common::optional, + common::optional +> NoJsonAndSerializationErrorWithCause( + std::wstring cause +) { + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + std::move(cause) + ) + ); +} + +/** + * \brief Give out a failed serialization with \p error. + * + * \param error of the serialization + * \return no value, and the error + */ +std::pair< + common::optional, + common::optional +> NoJsonAndSerializationError( + SerializationError error +) { + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + std::move(error) + ) + ); +} + +/** + * \brief Put the value serialized under \p key of \p result, or give out + * the error of the serialization, marked with \p property. + * + * We deliberately take the *result* of a serialization instead of the value + * and the function which serializes it. The item serializers of a tuple vary + * both in number and in type, so no signature taking the serializer could + * serve every serialization; taking the result lets this single function + * serve all of them. + * + * \param result object to be written to + * \param key of the property in the JSON object + * \param property which the key stands for, for the path of the error + * \param serialized result of the serialization + * \return the error, if the serialization failed + */ +common::optional SerializeInto( + nlohmann::json& result, + const char* key, + iteration::Property property, + std::pair< + common::optional, + common::optional + >&& serialized +) { + if (serialized.second.has_value()) { + serialized.second->path.segments.emplace_front( + common::make_unique(property) + ); + + return std::move(serialized.second); + } + + result[key] = std::move(*serialized.first); + + return common::nullopt; +} + +/** + * \brief Give the value out in the shape of a serialization which can fail. + * + * ``SerializeTuple{N}`` takes one item serializer per item, and the items of + * a tuple differ in kind, so some of them can fail and some of them can not. + * Lifting the ones which can not is what lets the tuple treat all of them + * alike, without a normalizing lambda per kind at the call site. + */ +template +std::pair< + common::optional, + common::optional +> AsFallible(T&& value) { + return std::make_pair( + common::make_optional(std::forward(value)), + common::nullopt + ); +} + +/** + * @copybrief AsFallible + * + * The serialization could already fail, so there is nothing to lift. + */ +inline std::pair< + common::optional, + common::optional +> AsFallible( + std::pair< + common::optional, + common::optional + >&& serialized +) { + return std::move(serialized); +} + +/** + * \brief Serialize \p that instance to a JSON value, dispatching on its + * model type. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeIClass( + const types::IClass& that +); + +/** + * \brief Serialize \p that instance of types::ISomeItem to a JSON value. + * + * \param that instance to be serialized + * \return the JSON value + */ +nlohmann::json SerializeConcreteSomeItem( + const types::ISomeItem& that +); + +/** + * \brief Serialize \p that instance of types::ISpecialItem to a JSON value. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeSpecialItem( + const types::ISpecialItem& that +); + +/** + * \brief Serialize \p that instance of types::IContainer to a JSON value. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeContainer( + const types::IContainer& that +); + +/** + * \brief Serialize \p that instance of types::IWrapper to a JSON value. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeWrapper( + const types::IWrapper& that +); + +/** + * \brief Serialize \p that instance of types::IItem to a JSON value, + * dispatching on its model type. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeItem( + const types::IItem& that +); + +/** + * \brief Serialize \p that instance of types::ISomeItem to a JSON value, + * dispatching on its model type. + * + * \param that instance to be serialized + * \return the JSON value , or an error, if any + */ +std::pair< + common::optional, + common::optional +> SerializeSomeItem( + const types::ISomeItem& that +); + +nlohmann::json SerializeConcreteSomeItem( + const types::ISomeItem& that +) { + nlohmann::json result = nlohmann::json::object(); + + result["name"] = SerializeWstring( + that.name() + ); + + const common::optional& maybe_nickname( + that.nickname() + ); + if (maybe_nickname.has_value()) { + result["nickname"] = SerializeWstring( + *maybe_nickname + ); + } + + result["modelType"] = "SomeItem"; + + return result; +} + +std::pair< + common::optional, + common::optional +> SerializeSpecialItem( + const types::ISpecialItem& that +) { + nlohmann::json result = nlohmann::json::object(); + + common::optional error; + + result["name"] = SerializeWstring( + that.name() + ); + + const common::optional& maybe_nickname( + that.nickname() + ); + if (maybe_nickname.has_value()) { + result["nickname"] = SerializeWstring( + *maybe_nickname + ); + } + + error = SerializeInto( + result, + "level", + iteration::Property::kLevel, + SerializeInt64( + that.level() + ) + ); + if (error.has_value()) { + return NoJsonAndSerializationError( + std::move(*error) + ); + } + + result["modelType"] = "SpecialItem"; + + return std::make_pair< + common::optional, + common::optional + >( + common::make_optional(std::move(result)), + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> SerializeContainer( + const types::IContainer& that +) { + nlohmann::json result = nlohmann::json::object(); + + common::optional error; + + error = SerializeInto( + result, + "items", + iteration::Property::kItems, + SerializeListOfInstancesWithFallible( + that.items(), + SerializeItem + ) + ); + if (error.has_value()) { + return NoJsonAndSerializationError( + std::move(*error) + ); + } + + error = SerializeInto( + result, + "someItems", + iteration::Property::kSomeItems, + SerializeListOfInstancesWithFallible( + that.some_items(), + SerializeSomeItem + ) + ); + if (error.has_value()) { + return NoJsonAndSerializationError( + std::move(*error) + ); + } + + const common::optional< + std::vector< + std::shared_ptr + > + >& maybe_special_items( + that.special_items() + ); + if (maybe_special_items.has_value()) { + error = SerializeInto( + result, + "specialItems", + iteration::Property::kSpecialItems, + SerializeListOfInstancesWithFallible( + *maybe_special_items, + SerializeSpecialItem + ) + ); + if (error.has_value()) { + return NoJsonAndSerializationError( + std::move(*error) + ); + } + } + + return std::make_pair< + common::optional, + common::optional + >( + common::make_optional(std::move(result)), + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> SerializeWrapper( + const types::IWrapper& that +) { + nlohmann::json result = nlohmann::json::object(); + + common::optional error; + + error = SerializeInto( + result, + "container", + iteration::Property::kContainer, + SerializeContainer( + *(that.container()) + ) + ); + if (error.has_value()) { + return NoJsonAndSerializationError( + std::move(*error) + ); + } + + return std::make_pair< + common::optional, + common::optional + >( + common::make_optional(std::move(result)), + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> SerializeItem( + const types::IItem& that +) { + // NOTE (mristin): + // The dynamic casts are necessary due to virtual inheritance. Otherwise, + // we would have used static casts. + + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return AsFallible( + SerializeConcreteSomeItem( + dynamic_cast(that) + ) + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItem( + dynamic_cast(that) + ); + default: { + std::string message = common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that.model_type() + ) + ) + ); + + throw std::invalid_argument(message); + } + }; +} + +std::pair< + common::optional, + common::optional +> SerializeSomeItem( + const types::ISomeItem& that +) { + // NOTE (mristin): + // The dynamic casts are necessary due to virtual inheritance. Otherwise, + // we would have used static casts. + + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return AsFallible( + SerializeConcreteSomeItem(that) + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItem( + dynamic_cast(that) + ); + default: { + std::string message = common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that.model_type() + ) + ) + ); + + throw std::invalid_argument(message); + } + }; +} + +std::pair< + common::optional, + common::optional +> SerializeIClass( + const types::IClass& that +) { + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return AsFallible( + SerializeConcreteSomeItem( + dynamic_cast(that) + ) + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItem( + dynamic_cast(that) + ); + case types::ModelType::kContainer: + return SerializeContainer( + dynamic_cast(that) + ); + case types::ModelType::kWrapper: + return SerializeWrapper( + dynamic_cast(that) + ); + default: { + std::string message = common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + that.model_type() + ) + ) + ); + + throw std::invalid_argument(message); + } + }; +} + +nlohmann::json Serialize( + const types::IClass& that +) { + common::optional result; + common::optional error; + + std::tie( + result, + error + ) = SerializeIClass(that); + + if (error.has_value()) { + throw SerializationException( + std::move(error->cause), + std::move(error->path) + ); + } + + return std::move(*result); +} + +// endregion Serialization + +} // namespace jsonization +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/pattern.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/pattern.cpp new file mode 100644 index 000000000..14eb2451e --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/pattern.cpp @@ -0,0 +1,15 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/pattern.hpp" +#include "dummy/revm.hpp" + +namespace dummy { +namespace pattern { + +} // namespace pattern +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/revm.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/revm.cpp new file mode 100644 index 000000000..9c3d9aa0d --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/revm.cpp @@ -0,0 +1,861 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/revm.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { +namespace revm { + +/** + * Represent the character such that it can be printed in the console. + * + * To that end, we escape the character if it is out of the printable ASCII set. + * + * This function is mostly meant for debugging purposes. + */ +std::string RepresentWCharacter(wchar_t character) { + switch (character) { + case L'\\':return "\\\\"; + case L'"':return "\\\""; + case L'\'':return "\\'"; + case L'\t':return "\\t"; + case L'\n':return "\\n"; + case L'\r':return "\\r"; + default: break; + } + + if (26 <= character && character <= 126) { + return std::string(1, static_cast(character)); + } + + static const char* digits = "0123456789ABCDEF"; + size_t digit_count = sizeof(wchar_t) * 2; + + std::string result; + result.resize(digit_count + 2); + result[0] = L'\\'; + result[1] = L'u'; + + for (size_t i = 0, j = (digit_count - 1) * 4; i < digit_count; ++i, j -= 4) { + const size_t digit_i = (character >> j) & 0x0f; + result[i + 2] = digits[digit_i]; + } + + return result; +} + +/** + * Represent the wide string for debugging purposes where it is printed to the console. + * + * To that end, we escape the individual characters if they are out of printable ASCII + * set. + */ +std::string RepresentWString(const std::wstring& text) { + std::vector parts; + parts.reserve(text.size()); + for (const wchar_t character : text) { + parts.emplace_back(RepresentWCharacter(character)); + } + + size_t size = 0; + for (const std::string& part : parts) { + size += part.size(); + } + + std::string result; + result.reserve(size); + for (const std::string& part : parts) { + result.append(part); + } + return result; +} + +InstructionChar::InstructionChar( + wchar_t a_character +) : + character(a_character) { + // Intentionally empty. +} + +InstructionKind InstructionChar::kind() const { + return InstructionKind::Char; +} + +std::string to_string(const InstructionChar& instruction) { + return common::Concat( + "char '", + RepresentWCharacter(instruction.character), + "'" + ); +} + +Range::Range( + wchar_t a_first, + wchar_t a_last +) : + first(a_first), + last(a_last) { + // NOTE (mristin): + // We are aware that exceptions in constructors should be avoided to prevent + // bug related to uninitialized object state. However, in this case, we do not + // see any risk for such a mistake. + if (a_first > a_last) { + throw std::invalid_argument( + common::Concat( + "The first character in a character range, ", + RepresentWCharacter(a_first), + ", is larger than the last character in the range, ", + RepresentWCharacter(a_last) + ) + ); + } +} + +std::string to_string(const Range& range) { + if (range.first == range.last) { + return RepresentWCharacter(range.first); + } + + return common::Concat( + RepresentWCharacter(range.first), + "-", + RepresentWCharacter(range.last) + ); +} + +InstructionSet::InstructionSet( + std::vector a_ranges +) : + ranges(std::move(a_ranges)) { + // NOTE (mristin): + // We are aware that exceptions in constructors should be avoided to prevent + // bug related to uninitialized object state. However, in this case, we do not + // see any risk for such a mistake. + if (ranges.empty()) { + throw std::invalid_argument( + "Unexpected NotSet instruction with empty ranges" + ); + } + + for (size_t i = 1; i < ranges.size(); ++i) { + if (ranges[i - 1].last >= ranges[i].first) { + throw std::invalid_argument( + common::Concat( + "The ranges for an InstructionSet are unexpectedly either " + "not sorted or overlapping. The range at index ", + std::to_string(i - 1), + " is ", + to_string(ranges[i - 1]), + " and the range at index ", + std::to_string(i), + " is ", + to_string(ranges[i]) + ) + ); + } + } +} + +InstructionKind InstructionSet::kind() const { + return InstructionKind::Set; +} + +std::string to_string(const InstructionSet& instruction) { + std::stringstream ss; + ss << "set '"; + + for (const auto& range : instruction.ranges) { + ss << to_string(range); + } + + ss << "'"; + return ss.str(); +} + +InstructionNotSet::InstructionNotSet( + std::vector a_ranges +) : + ranges(std::move(a_ranges)) { + // NOTE (mristin): + // We are aware that exceptions in constructors should be avoided to prevent + // bug related to uninitialized object state. However, in this case, we do not + // see any risk for such a mistake. + if (ranges.empty()) { + throw std::invalid_argument( + "Unexpected NotSet instruction with empty ranges" + ); + } + + for (size_t i = 1; i < ranges.size(); ++i) { + if (ranges[i - 1].last >= ranges[i].first) { + throw std::invalid_argument( + common::Concat( + "The ranges for an InstructionNotSet are unexpectedly either " + "not sorted or overlapping. The range at index ", + std::to_string(i - 1), + " is ", + to_string(ranges[i - 1]), + " and the range at index ", + std::to_string(i), + " is ", + to_string(ranges[i]) + ) + ); + } + } +} + +InstructionKind InstructionNotSet::kind() const { + return InstructionKind::NotSet; +} + +std::string to_string(const InstructionNotSet& instruction) { + std::stringstream ss; + ss << "not-set '"; + + for (const auto& range : instruction.ranges) { + ss << to_string(range); + } + + ss << "'"; + return ss.str(); +} + +InstructionKind InstructionAny::kind() const { + return InstructionKind::Any; +} + +std::string to_string(const InstructionAny&) { + return "any"; +} + +InstructionKind InstructionMatch::kind() const { + return InstructionKind::Match; +} + +std::string to_string(const InstructionMatch&) { + return "match"; +} + +InstructionJump::InstructionJump( + size_t a_target +) : + target(a_target) { + // Intentionally empty. +} + +InstructionKind InstructionJump::kind() const { + return InstructionKind::Jump; +} + +std::string to_string(const InstructionJump& instruction) { + return common::Concat( + "jump ", + std::to_string(instruction.target) + ); +} + +InstructionSplit::InstructionSplit( + size_t a_first_target, + size_t a_second_target +) : + first_target(a_first_target), + second_target(a_second_target) { + // Intentionally empty. +} + +InstructionKind InstructionSplit::kind() const { + return InstructionKind::Split; +} + +std::string to_string(const InstructionSplit& instruction) { + return common::Concat( + "split ", + std::to_string(instruction.first_target), + ", ", + std::to_string(instruction.second_target) + ); +} + +InstructionKind InstructionEnd::kind() const { + return InstructionKind::End; +} + +std::string to_string(const InstructionEnd&) { + return "end"; +} + +std::string to_string(const Instruction& instruction) { + switch (instruction.kind()) { + case InstructionKind::Char: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionChar& + >(instruction) + ); + + case InstructionKind::Set: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSet& + >(instruction) + ); + + case InstructionKind::NotSet: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionNotSet& + >(instruction) + ); + + case InstructionKind::Any: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionAny& + >(instruction) + ); + + case InstructionKind::Match: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionMatch& + >(instruction) + ); + + case InstructionKind::Jump: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionJump& + >(instruction) + ); + + case InstructionKind::Split: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSplit& + >(instruction) + ); + + case InstructionKind::End: + return to_string( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionEnd& + >(instruction) + ); + + default: + throw std::logic_error( + common::Concat( + "Unhandled instruction kind: ", + std::to_string( + static_cast(instruction.kind()) + ) + ) + ); + } +} + +std::string to_string( + const std::vector >& instructions +) { + std::stringstream ss; + for (size_t i = 0; i < instructions.size(); ++i) { + ss + << std::setw(4) << i << ": " + << to_string(*instructions[i]) << std::endl; + } + + return ss.str(); +} + +bool CharacterInRanges( + const std::vector& ranges, + wchar_t character +) { + if (ranges.empty()) { + return false; + } + + if (ranges.size() == 1) { + return (ranges[0].first <= character && character <= ranges[0].last); + } + + // Binary search + size_t begin = 0; + size_t end = ranges.size(); + + while (true) { + if (begin == end) { + return false; + } + + // NOTE (mristin): + // Most implementations of the binary search are buggy, see: + // https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues. + // + // We try to avert some of the bugs by explicitly handling the case where there + // are at most 3 elements in the segment, so we stop here instead of proceeding + // recursively. + if (end - begin <= 3) { + for (size_t i = begin; i < end; ++i) { + const Range& range = ranges[i]; + if (range.first <= character && character <= range.last) { + return true; + } + } + return false; + } + + const size_t middle = (begin + end) / 2; + const Range& range = ranges[middle]; + if (character < range.first) { + end = middle; + } else if (character > range.last) { + begin = middle; + } else { + return true; + } + } +} + +/** + * Keep track of the threads currently being executed. + */ +class ThreadList { + public: + explicit ThreadList(size_t program_size) { + has_.resize(program_size, false); + items_.reserve(program_size); + } + + /** + * Add a new thread for the given program counter if it is not already in the list. + */ + void Spawn(size_t program_counter) { + #ifdef DEBUG + if (program_counter >= has_.size()) { + throw std::invalid_argument( + common::Concat( + "Unexpected spawning of a thread at the program counter ", + std::to_string(program_counter), + " since the program size was indicated to be ", + std::to_string(has_.size()) + ) + ); + } + #endif + + if (has_[program_counter]) { + return; + } + + has_[program_counter] = true; + items_.push_back(program_counter); + } + + bool Empty() const { + return items_.empty(); + } + + /** + * Pop the thread from the back, returning its program counter. + * + * The order of the threads is not guaranteed. + */ + size_t Pop() { + #ifdef DEBUG + if (items_.empty()) { + throw std::logic_error( + "You tried to pop from an empty thread list." + ); + } + #endif + + const size_t program_counter = items_.back(); + items_.pop_back(); + has_[program_counter] = false; + return program_counter; + } + + /** + * Clear the thread list, keeping its memory capacity. + */ + void Clear() { + std::fill(has_.begin(), has_.end(), false); + items_.clear(); + } + + /** + * Return the program counters corresponding to the spawned threads. + */ + const std::vector& Items() const { + return items_; + }; + + private: + /** + * Keep track of the program counters corresponding to the threads so that we can + * avoid the duplicate threads. + */ + std::vector has_; + + /** + * Keep track of the active threads. + */ + std::vector items_; +}; + +std::string to_string(const ThreadList& thread_list) { + if (thread_list.Empty()) { + return "[]"; + } + + std::vector items(thread_list.Items()); + std::sort(items.begin(), items.end()); + + std::stringstream ss; + ss << "["; + ss << std::to_string(items[0]); + + for (size_t i = 1; i < items.size(); ++i) { + ss << ", " << items[i]; + } + ss << "]"; + + return ss.str(); +} + +/** + * Try to match the program against the text. + * + * @return true if the text matches + */ +bool Match( + const std::vector >& program, + const std::wstring& text +) { + if (program.empty()) { + return false; + } + + // NOTE (mristin): + // We validate at the beginning so that we can avoid checks in + // the instruction loops. + for (size_t i = 0; i < program.size(); ++i) { + const Instruction& instruction = *program[i]; + + switch (instruction.kind()) { + case InstructionKind::Jump: { + const auto& instruction_jump( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionJump& + >( + instruction + ) + ); + + if (instruction_jump.target >= program.size()) { + throw std::invalid_argument( + common::Concat( + "Unexpected jump beyond the end of the program. Program has ", + std::to_string(program.size()), + " instruction(s), but the instruction ", + std::to_string(i), + " wants to jump to ", + std::to_string(instruction_jump.target) + ) + ); + } + break; + } + + case InstructionKind::Split: { + const auto& instruction_split( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSplit& + >( + instruction + ) + ); + + if (instruction_split.first_target >= program.size()) { + throw std::invalid_argument( + common::Concat( + "Unexpected split & jump beyond the end of the program. Program has ", + std::to_string(program.size()), + " instruction(s), but the instruction ", + std::to_string(i), + " wants to split and make the first jump to ", + std::to_string(instruction_split.first_target) + ) + ); + } + + if (instruction_split.second_target >= program.size()) { + throw std::invalid_argument( + common::Concat( + "Unexpected split & jump beyond the end of the program. Program has ", + std::to_string(program.size()), + " instruction(s), but the instruction ", + std::to_string(i), + " wants to split and make the second jump to ", + std::to_string(instruction_split.second_target) + ) + ); + } + + break; + } + + default: + continue; + } + } + + // NOTE (mristin): + // See: https://swtch.com/~rsc/regexp/regexp2.html, + // Section "Thompson's Implementation". + + std::unique_ptr clist(common::make_unique(program.size())); + std::unique_ptr nlist(common::make_unique(program.size())); + + clist->Spawn(0); + + for (const wchar_t character : text) { + #ifdef DEBUG + if (!nlist->Empty()) { + throw std::logic_error( + "Expected the list of next-to-be-executed threads to be empty, " + "but it was not." + ); + } + #endif + + while (!clist->Empty()) { + const size_t program_counter = clist->Pop(); + + #ifdef DEBUG + if (program_counter >= program.size()) { + throw std::logic_error( + common::Concat( + "Unexpected program counter beyond the program. The program size was ", + std::to_string(program.size()), + ", while the program counter of a thread was ", + std::to_string(program_counter) + ) + ); + } + #endif + + const Instruction& instruction = *program[program_counter]; + + switch (instruction.kind()) { + case InstructionKind::Char: { + const auto& instruction_char( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionChar& + >(instruction) + ); + + if (character != instruction_char.character) { + // The matching failed for this thread. + break; + } + + nlist->Spawn(program_counter + 1); + break; + } + + case InstructionKind::Set: { + const auto& instruction_set( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSet& + >(instruction) + ); + + if (!CharacterInRanges(instruction_set.ranges, character)) { + // The matching failed for this thread. + break; + } + + nlist->Spawn(program_counter + 1); + break; + } + + case InstructionKind::NotSet: { + const auto& instruction_not_set( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionNotSet& + >(instruction) + ); + + if (CharacterInRanges(instruction_not_set.ranges, character)) { + // The matching failed for this thread. + break; + } + + nlist->Spawn(program_counter + 1); + break; + } + + case InstructionKind::Any: { + // NOTE (mristin): + // We simply proceed to the next instruction at the next character without + // any checks. + nlist->Spawn(program_counter + 1); + break; + } + + case InstructionKind::Match: + return true; + + case InstructionKind::Jump: { + const auto& instruction_jump( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionJump& + >(instruction) + ); + + clist->Spawn(instruction_jump.target); + break; + } + + case InstructionKind::Split: { + const auto& instruction_split( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSplit& + >(instruction) + ); + + clist->Spawn(instruction_split.first_target); + clist->Spawn(instruction_split.second_target); + break; + } + + case InstructionKind::End: { + // The matching failed for this thread as we have just consumed + // a character. + break; + } + + default: + throw std::logic_error( + common::Concat( + "Unhandled instruction kind: ", + std::to_string( + static_cast(instruction.kind()) + ) + ) + ); + } + } + + std::swap(clist, nlist); + nlist->Clear(); + } + + // NOTE (mristin): + // We need to process any pending jumps, splits and matches even tough there are + // no more characters to consume. + while (!clist->Empty()) { + const size_t program_counter = clist->Pop(); + + #ifdef DEBUG + if (program_counter >= program.size()) { + throw std::logic_error( + common::Concat( + "Unexpected program counter beyond the program. The program size was ", + std::to_string(program.size()), + ", while the program counter of a thread was ", + std::to_string(program_counter) + ) + ); + } + #endif + + const Instruction& instruction = *program[program_counter]; + + switch (instruction.kind()) { + case InstructionKind::Char: { // NOLINT(bugprone-branch-clone) + // We reached the end-of-input so there are no characters to be matched. + // This thread needs therefore to die. + break; + } + + case InstructionKind::Set: { + // We reached the end-of-input so there are no character sets to be matched. + // This thread needs therefore to die. + break; + } + + case InstructionKind::NotSet: { + // We reached the end-of-input so there are no character sets to be matched. + // This thread needs therefore to die. + break; + } + + case InstructionKind::Any: { + // We reached the end-of-input so there are no characters to be matched. + // This thread needs therefore to die. + break; + } + + case InstructionKind::Match: + return true; + + case InstructionKind::Jump: { + const auto& instruction_jump( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionJump& + >(instruction) + ); + + clist->Spawn(instruction_jump.target); + break; + } + + case InstructionKind::Split: { + const auto& instruction_split( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const InstructionSplit& + >(instruction) + ); + + clist->Spawn(instruction_split.first_target); + clist->Spawn(instruction_split.second_target); + break; + } + + case InstructionKind::End: { + // We reached the end-of-input so we match and move to the next instruction. + clist->Spawn(program_counter + 1); + break; + } + + default: + throw std::logic_error( + common::Concat( + "Unhandled instruction kind: ", + std::to_string( + static_cast(instruction.kind()) + ) + ) + ); + } + } + + return false; +} + +} // namespace revm +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/stringification.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/stringification.cpp new file mode 100644 index 000000000..14c78c877 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/stringification.cpp @@ -0,0 +1,375 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/stringification.hpp" + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +namespace dummy { +namespace stringification { + +const std::unordered_map< + std::string, + types::ModelType +> kModelTypeFromStringMap = { + { + "SomeItem", + types::ModelType::kSomeItem + }, + { + "SpecialItem", + types::ModelType::kSpecialItem + }, + { + "Container", + types::ModelType::kContainer + }, + { + "Wrapper", + types::ModelType::kWrapper + } +}; + +common::optional ModelTypeFromString( + const std::string& text +) { + const auto it = kModelTypeFromStringMap.find( + text + ); + if (it == kModelTypeFromStringMap.end()) { + return {}; + } + return it->second; +} + +types::ModelType MustModelTypeFromString( + const std::string& text +) { + const auto it = kModelTypeFromStringMap.find( + text + ); + if (it == kModelTypeFromStringMap.end()) { + throw std::invalid_argument( + common::Concat( + "Unexpected ModelType literal: ", + text + ) + ); + } + return it->second; +} + +std::string to_string( + types::ModelType model_type +) { + switch (model_type) { + case types::ModelType::kSomeItem: + return "SomeItem"; + case types::ModelType::kSpecialItem: + return "SpecialItem"; + case types::ModelType::kContainer: + return "Container"; + case types::ModelType::kWrapper: + return "Wrapper"; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast( + model_type + ) + ) + ) + ); + } +} + +// The following encoder has been adapted from Jouni Malinen to work with +// std::string. The original source code is available at: +// https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c +// +// See also the following StackOverflow question for a benchmark: +// https://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c/41094722#41094722 + +constexpr std::size_t kCharBase64TableLen = 65; +static const unsigned char kCharBase64Table[kCharBase64TableLen]( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +); + +std::string Base64Encode( + const std::vector& bytes +) { + // See: https://cplusplus.com/reference/vector/vector/data/. + // The data is guaranteed to be a continuous block in memory. + const unsigned char* const src( + bytes.data() + ); + + const std::size_t len = bytes.size(); + + // 3-byte blocks to 4-byte + const std::size_t olen = 4 * ((len + 2) / 3); + + // Integer overflow? + if (olen < len) { + throw std::invalid_argument( + common::Concat( + "The calculation of the output length overflowed. " + "The length was: ", + std::to_string(len), + ", but the output length was calculated as: ", + std::to_string(olen) + ) + ); + } + + std::string out_string; + out_string.resize(olen); + + unsigned char* out( + reinterpret_cast( + &out_string[0] + ) + ); + + const unsigned char* const end = src + len; + + const unsigned char* in = src; + unsigned char* pos = out; + + while (end - in >= 3) { + *pos++ = kCharBase64Table[in[0] >> 2]; + *pos++ = kCharBase64Table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; + *pos++ = kCharBase64Table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; + *pos++ = kCharBase64Table[in[2] & 0x3f]; + in += 3; + } + + if (end - in) { + *pos++ = kCharBase64Table[in[0] >> 2]; + + if (end - in == 1) { + *pos++ = kCharBase64Table[(in[0] & 0x03) << 4]; + *pos++ = '='; + } else { + *pos++ = kCharBase64Table[ + ((in[0] & 0x03) << 4) | (in[1] >> 4) + ]; + *pos++ = kCharBase64Table[(in[1] & 0x0f) << 2]; + } + *pos++ = '='; + } + + return out_string; +} + +// The following decoder is vaguely based on: +// https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js, +// https://github.com/niklasvh/base64-arraybuffer/blob/master/src/index.ts and +// https://github.com/beatgammit/base64-js/blob/master/index.js. + +std::vector ConstructBase64Lookup() { + std::vector lookup(256, 255); + for (std::uint8_t i = 0; i < kCharBase64TableLen; ++i) { + lookup.at(kCharBase64Table[i]) = i; + } + return lookup; +} +const std::vector kBase64Lookup = ConstructBase64Lookup(); + +common::expected< + std::vector, + std::string +> Base64Decode( + const std::string& text +) { + if (text.empty()) { + return std::vector(); + } + + const std::size_t len = text.size(); + std::size_t len_wo_pad = len; + + // NOTE (mristin): + // Some implementations forget the padding, so we try to be robust and check + // for the padding manually. + std::size_t bytes_length = (len * 3) / 4; + if (text[len - 1] == '=') { + bytes_length--; + len_wo_pad--; + + if (text[len - 2] == '=') { + bytes_length--; + len_wo_pad--; + } + } + + std::vector bytes(bytes_length); + + const std::size_t base64_lookup_len = kBase64Lookup.size(); + + std::size_t pointer = 0; + + for (std::size_t i = 0; i < len; i += 4) { + // NOTE (mristin): + // Admittedly, this is very verbose code, but we want to be efficient, so we + // opted for performance over readability here. + + const unsigned char code0 = text[i]; + if (code0 >= base64_lookup_len) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i), + ": ", + std::to_string(code0), + " (code: ", + std::to_string(static_cast(code0)), + ")" + ); + + return common::make_unexpected(message); + } + const std::uint8_t encoded0 = kBase64Lookup[code0]; + if (encoded0 == 255) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i), + ": ", + std::to_string(code0), + " (code: ", + std::to_string(static_cast(code0)), + ")" + ); + + return common::make_unexpected(message); + } + + const unsigned char code1 = text[i + 1]; + if (code1 >= base64_lookup_len) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 1), + ": ", + std::to_string(code1), + " (code: ", + std::to_string(static_cast(code1)), + ")" + ); + + return common::make_unexpected(message); + } + const std::uint8_t encoded1 = kBase64Lookup[code1]; + if (encoded1 == 255) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 1), + ": ", + std::to_string(code1), + " (code: ", + std::to_string(static_cast(code1)), + ")" + ); + + return common::make_unexpected(message); + } + + // We map padding to 65, which is the value of "A". + const unsigned char code2 = i + 2 < len_wo_pad ? text[i + 2] : 65; + if (code2 >= base64_lookup_len) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 2), + ": ", + std::to_string(code2), + " (code: ", + std::to_string(static_cast(code2)), + ")" + ); + + return common::make_unexpected(message); + } + const std::uint8_t encoded2 = kBase64Lookup[code2]; + if (encoded2 == 255) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 2), + ": ", + std::to_string(code2), + " (code: ", + std::to_string(static_cast(code2)), + ")" + ); + + return common::make_unexpected(message); + } + + // We map padding to 65, which is the value of 'A'. + const unsigned char code3 = i + 3 < len_wo_pad ? text[i + 3] : 65; + if (code3 >= base64_lookup_len) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 3), + ": ", + std::to_string(code3), + " (code: ", + std::to_string(static_cast(code3)), + ")" + ); + + return common::make_unexpected(message); + } + const std::uint8_t encoded3 = kBase64Lookup[code3]; + if (encoded3 == 255) { + std::string message = common::Concat( + "Expected a valid character from base64-encoded string, " + "but got at index ", + std::to_string(i + 3), + ": ", + std::to_string(code3), + " (code: ", + std::to_string(static_cast(code3)), + ")" + ); + + return common::make_unexpected(message); + } + + if (pointer >= bytes_length) { + break; + } + bytes[pointer] = (encoded0 << 2) | (encoded1 >> 4); + pointer++; + + if (pointer >= bytes_length) { + break; + } + bytes[pointer] = ((encoded1 & 15) << 4) | (encoded2 >> 2); + pointer++; + + if (pointer >= bytes_length) { + break; + } + bytes[pointer] = ((encoded2 & 3) << 6) | (encoded3 & 63); + pointer++; + } + + return bytes; +} + +} // namespace stringification +/**@}*/ + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/types.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/types.cpp new file mode 100644 index 000000000..6dac0313f --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/types.cpp @@ -0,0 +1,375 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/constants.hpp" +#include "dummy/types.hpp" + +namespace dummy { +namespace types { + +// region SomeItem + +SomeItem::SomeItem( + std::wstring name, + common::optional nickname +) { + name_ = std::move(name); + + nickname_ = std::move(nickname); +} + +ModelType SomeItem::model_type() const { + return ModelType::kSomeItem; +} + +const std::wstring& SomeItem::name() const { + return name_; +} + +std::wstring& SomeItem::mutable_name() { + return name_; +} + +void SomeItem::set_name( + std::wstring value +) { + name_ = value; +} + +const common::optional& SomeItem::nickname() const { + return nickname_; +} + +common::optional& SomeItem::mutable_nickname() { + return nickname_; +} + +void SomeItem::set_nickname( + common::optional value +) { + nickname_ = value; +} + +// endregion SomeItem + +// region SpecialItem + +SpecialItem::SpecialItem( + std::wstring name, + int64_t level, + common::optional nickname +) { + name_ = std::move(name); + + nickname_ = std::move(nickname); + + level_ = level; +} + +ModelType SpecialItem::model_type() const { + return ModelType::kSpecialItem; +} + +const std::wstring& SpecialItem::name() const { + return name_; +} + +std::wstring& SpecialItem::mutable_name() { + return name_; +} + +void SpecialItem::set_name( + std::wstring value +) { + name_ = value; +} + +const common::optional& SpecialItem::nickname() const { + return nickname_; +} + +common::optional& SpecialItem::mutable_nickname() { + return nickname_; +} + +void SpecialItem::set_nickname( + common::optional value +) { + nickname_ = value; +} + +int64_t SpecialItem::level() const { + return level_; +} + +int64_t& SpecialItem::mutable_level() { + return level_; +} + +void SpecialItem::set_level( + int64_t value +) { + level_ = value; +} + +// endregion SpecialItem + +// region Container + +Container::Container( + std::vector< + std::shared_ptr + > items, + std::vector< + std::shared_ptr + > some_items, + common::optional< + std::vector< + std::shared_ptr + > + > special_items +) { + items_ = std::move(items); + + some_items_ = std::move(some_items); + + special_items_ = std::move(special_items); +} + +ModelType Container::model_type() const { + return ModelType::kContainer; +} + +const std::vector< + std::shared_ptr +>& Container::items() const { + return items_; +} + +std::vector< + std::shared_ptr +>& Container::mutable_items() { + return items_; +} + +void Container::set_items( + std::vector< + std::shared_ptr + > value +) { + items_ = value; +} + +const std::vector< + std::shared_ptr +>& Container::some_items() const { + return some_items_; +} + +std::vector< + std::shared_ptr +>& Container::mutable_some_items() { + return some_items_; +} + +void Container::set_some_items( + std::vector< + std::shared_ptr + > value +) { + some_items_ = value; +} + +const common::optional< + std::vector< + std::shared_ptr + > +>& Container::special_items() const { + return special_items_; +} + +common::optional< + std::vector< + std::shared_ptr + > +>& Container::mutable_special_items() { + return special_items_; +} + +void Container::set_special_items( + common::optional< + std::vector< + std::shared_ptr + > + > value +) { + special_items_ = value; +} + +int64_t Container::CountItems( + const std::vector< + std::shared_ptr + >& items +) const { + return items.size(); +} + +// endregion Container + +// region Wrapper + +Wrapper::Wrapper( + std::shared_ptr container +) { + container_ = std::move(container); +} + +ModelType Wrapper::model_type() const { + return ModelType::kWrapper; +} + +const std::shared_ptr& Wrapper::container() const { + return container_; +} + +std::shared_ptr& Wrapper::mutable_container() { + return container_; +} + +void Wrapper::set_container( + std::shared_ptr value +) { + container_ = value; +} + +// endregion Wrapper + +// region Is-a functions + +bool IsItem( + const IClass& that +) { + switch (that.model_type()) { + case ModelType::kSomeItem: + return true; + case ModelType::kSpecialItem: + return true; + case ModelType::kContainer: + return false; + case ModelType::kWrapper: + return false; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that.model_type()) + ) + ) + ); + } +} + +bool IsSomeItem( + const IClass& that +) { + switch (that.model_type()) { + case ModelType::kSomeItem: + return true; + case ModelType::kSpecialItem: + return true; + case ModelType::kContainer: + return false; + case ModelType::kWrapper: + return false; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that.model_type()) + ) + ) + ); + } +} + +bool IsSpecialItem( + const IClass& that +) { + switch (that.model_type()) { + case ModelType::kSomeItem: + return false; + case ModelType::kSpecialItem: + return true; + case ModelType::kContainer: + return false; + case ModelType::kWrapper: + return false; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that.model_type()) + ) + ) + ); + } +} + +bool IsContainer( + const IClass& that +) { + switch (that.model_type()) { + case ModelType::kSomeItem: + return false; + case ModelType::kSpecialItem: + return false; + case ModelType::kContainer: + return true; + case ModelType::kWrapper: + return false; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that.model_type()) + ) + ) + ); + } +} + +bool IsWrapper( + const IClass& that +) { + switch (that.model_type()) { + case ModelType::kSomeItem: + return false; + case ModelType::kSpecialItem: + return false; + case ModelType::kContainer: + return false; + case ModelType::kWrapper: + return true; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that.model_type()) + ) + ) + ); + } +} + +// endregion Is-a functions + +} // namespace types +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/verification.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/verification.cpp new file mode 100644 index 000000000..ea55dcf18 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/verification.cpp @@ -0,0 +1,1246 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/constants.hpp" +#include "dummy/pattern.hpp" +#include "dummy/revm.hpp" +#include "dummy/stringification.hpp" +#include "dummy/verification.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { +namespace verification { + +// region struct Error + +Error::Error( + std::wstring a_cause +) : + cause(std::move(a_cause)) { + // Intentionally empty. +} + +Error::Error( + std::wstring a_cause, + iteration::Path a_path +) : + cause(std::move(a_cause)), + path(std::move(a_path)) { + // Intentionally empty. +} + +// endregion struct Error + +// region Verification functions + +bool ContainsSpecialItem( + const std::shared_ptr& special_item, + const std::vector< + std::shared_ptr + >& items +) { + return ( + common::Contains( + items, + special_item + ) + ); +} + +bool ContainerNamesAreUnique( + const std::shared_ptr& container +) { + const auto& some_items = container->some_items(); + const std::vector >& all_some_items = container->some_items(); + return ( + ( + NamesAreUnique( + some_items + ) + && NamesAreUnique( + all_some_items + ) + && OptionalNamesAreUnique( + common::make_optional( + all_some_items + ) + ) + ) + ); +} + +bool LevelsArePositive( + const std::vector< + std::shared_ptr + >& items +) { + return ( + common::All( + [&](const std::shared_ptr& item) -> bool { + return item->level() > 0; + }, + items + ) + ); +} + +// endregion Verification functions + +namespace { + +/** + * \brief Enumerate the shapes of the values that we verify. + * + * A shape tells which checks apply to a value, see \ref ChecksOf. + */ +enum class Shape : std::uint32_t { + kContainer = 0, + kWrapper = 1 +}; // enum class Shape + +/** + * \brief Represent a single check of a value. + * + * The checks of a shape are listed in \ref ChecksOf. + */ +struct Check { + /** + * Check that the invariant holds for the value + */ + bool (*holds)(const void* value); + + /** + * Human-readable description of the invariant, reported if it does not hold + */ + const wchar_t* message; +}; // struct Check + +// region Checks + +bool Container_0( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return !(that->special_items().has_value()) + || common::All( + [&](const std::shared_ptr& special_item) -> bool { + return verification::ContainsSpecialItem( + special_item, + (*(that->special_items())) + ); + }, + (*(that->special_items())) + ); +} + +bool Container_1( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return ( + that->some_items().size() == 0 + || verification::ItemsOverlap( + that->items(), + that->some_items() + ) + ); +} + +bool Container_2( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return common::All( + [&](const std::shared_ptr& some_item) -> bool { + return verification::ContainsSomeItem( + some_item, + that->items() + ); + }, + that->some_items() + ); +} + +bool Container_3( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::AliasedNamesAreUnique( + that->some_items(), + that->special_items() + ); +} + +bool Container_4( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::NamesAreDisjoint( + that->items(), + that->some_items(), + L"x" + ); +} + +bool Container_5( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return !verification::SomeSpecialItemHasLevel( + that->items() + ) + || verification::LastIsSpecial( + that->items() + ); +} + +bool Container_6( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::LevelsOfSpecialItemsArePositive( + that->items() + ); +} + +bool Container_7( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::NicknamesAreUnique( + that->some_items() + ); +} + +bool Container_8( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return that->CountItems( + that->items() + ) == static_cast(that->items().size()); +} + +bool Container_9( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::OptionalNamesAreUnique( + that->special_items() + ); +} + +bool Container_10( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return !(that->special_items().has_value()) + || verification::FirstIsSpecial( + (*(that->special_items())) + ); +} + +bool Container_11( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return !(that->special_items().has_value()) + || verification::LevelsArePositive( + (*(that->special_items())) + ); +} + +bool Container_12( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return !verification::SomeSpecialItemHasLevel( + that->items() + ) + || verification::FirstIsSpecial( + that->items() + ); +} + +bool Container_13( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::NamesAreNonEmpty( + that->some_items() + ); +} + +bool Container_14( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::SomeNamesAreUnique( + that->some_items(), + that->special_items() + ); +} + +bool Container_15( + const void* value +) { + const types::IContainer* that = ( + static_cast(value) + ); + return verification::NamesAreUnique( + that->items() + ); +} + +bool Wrapper_0( + const void* value +) { + const types::IWrapper* that = ( + static_cast(value) + ); + return verification::ContainerNamesAreUnique( + that->container() + ); +} + +/** + * Give out the checks of the values of the \p shape. + */ +const std::vector& ChecksOf(Shape shape) { + switch (shape) { + case Shape::kContainer: { + static const std::vector checks = { + { + &Container_0, + L"Each special item must be among the special items." + }, + { + &Container_1, + L"If there are some items, they must overlap with the items." + }, + { + &Container_2, + L"Some items must be among the items." + }, + { + &Container_3, + L"Names of some items and of special items must be unique " + L"through aliases." + }, + { + &Container_4, + L"Names of the items and of some items must be disjoint." + }, + { + &Container_5, + L"If there is a special item with level, the last item must " + L"be special." + }, + { + &Container_6, + L"Levels of the special items among the items must be " + L"positive." + }, + { + &Container_7, + L"Nicknames of some items must be unique." + }, + { + &Container_8, + L"Count of the items must match the number of the items." + }, + { + &Container_9, + L"Names of the special items must be unique." + }, + { + &Container_10, + L"The first special item must be special." + }, + { + &Container_11, + L"Levels of the special items must be positive." + }, + { + &Container_12, + L"If there is a special item with level, the first item must " + L"be special." + }, + { + &Container_13, + L"Names of some items must be non-empty." + }, + { + &Container_14, + L"Names of some items and of special items must be unique." + }, + { + &Container_15, + L"Names of the items must be unique." + } + }; + return checks; + } + case Shape::kWrapper: { + static const std::vector checks = { + { + &Wrapper_0, + L"Names of some items of the container must be unique." + } + }; + return checks; + } + default: + throw std::logic_error( + common::Concat( + "Unexpected shape: ", + std::to_string(static_cast(shape)) + ) + ); + } +} + +// endregion Checks + +/** + * \brief Create the nested verification of the \p value, if its \p shape has one. + * + * \return nullptr if the shape has no nested verification + */ +std::unique_ptr NewNestedVerificator( + Shape, + const void* +) { + // NOTE (mristin): + // The meta-model uses no JSON-able values, so no value needs a nested + // verification. + return nullptr; +} + +// region Iteration over the values + +/** + * \brief Iterate lazily over the values to be verified. + * + * Every value comes with its \ref Shape, which tells which checks apply to it. + * + * We build no paths while iterating. The path to the current value is built only + * when an error has been found, see \ref AppendToPath. + * + * The iterators are combined out of the combinators below. They follow three rules + * so that we never build the iterators over the whole model up front: + * 1. \ref ChainIterator starts a child only once the previous child is done. + * 2. \ref DispatchingIterator dispatches on the instance only in \ref Start. + * 3. \ref EachIterator builds the iterator over an item only once the iteration + * reaches the item. + * + * Under these rules, every combinator is cheap to construct eagerly. + */ +class IIterator { + public: + /** + * Position at the first value, or become done if there are no values. + */ + virtual void Start() = 0; + + /** + * Move to the next value, or become done if there are no more values. + */ + virtual void Next() = 0; + + virtual bool Done() const = 0; + + /** + * \brief Point to the current value. + * + * The pointer is valid only until the next call to \ref Next. + */ + virtual const void* Value() const = 0; + + virtual Shape ShapeOf() const = 0; + + /** + * \brief Append the segments leading to the current value to the \p path. + * + * Only called when an error is found, so the iteration itself builds no paths. + */ + virtual void AppendToPath(iteration::Path& path) const = 0; + + virtual std::unique_ptr Clone() const = 0; + + virtual ~IIterator() = default; +}; // class IIterator + +/** + * Iterate over no values at all. + */ +class EmptyIterator : public IIterator { + public: + void Start() override { + // Intentionally empty. + } + + void Next() override { + throw std::logic_error( + "You want to move an EmptyIterator, but it is always done." + ); + } + + bool Done() const override { + return true; + } + + const void* Value() const override { + throw std::logic_error( + "You want to get a value from an EmptyIterator, but it is always done." + ); + } + + Shape ShapeOf() const override { + throw std::logic_error( + "You want to get a shape from an EmptyIterator, but it is always done." + ); + } + + void AppendToPath(iteration::Path&) const override { + throw std::logic_error( + "You want to append the path of an EmptyIterator, but it is always done." + ); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } +}; // class EmptyIterator + +std::unique_ptr Empty() { + return common::make_unique(); +} + +/** + * Iterate over a single value which lives in the model. + */ +class OneIterator : public IIterator { + public: + OneIterator( + const void* value, + Shape shape + ) : + value_(value), + shape_(shape), + done_(true) { + // Intentionally empty. + } + + void Start() override { + done_ = false; + } + + void Next() override { + done_ = true; + } + + bool Done() const override { + return done_; + } + + const void* Value() const override { + return value_; + } + + Shape ShapeOf() const override { + return shape_; + } + + void AppendToPath(iteration::Path&) const override { + // Intentionally empty, as the value itself is the end of the path. + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + const void* value_; + Shape shape_; + bool done_; +}; // class OneIterator + +std::unique_ptr One( + const void* value, + Shape shape +) { + return common::make_unique(value, shape); +} + +/** + * \brief Iterate over the values of the children, one child after another. + * + * A child is started only once the previous child is done. + */ +class ChainIterator : public IIterator { + public: + explicit ChainIterator( + std::vector > children + ) : + children_(std::move(children)), + active_(0) { + // Intentionally empty. + } + + ChainIterator(const ChainIterator& other) : + active_(other.active_) { + children_.reserve(other.children_.size()); + for (const std::unique_ptr& child : other.children_) { + children_.emplace_back(child->Clone()); + } + } + + void Start() override { + active_ = 0; + if (!children_.empty()) { + children_[0]->Start(); + } + SkipDoneChildren(); + } + + void Next() override { + children_[active_]->Next(); + SkipDoneChildren(); + } + + bool Done() const override { + return active_ >= children_.size(); + } + + const void* Value() const override { + return children_[active_]->Value(); + } + + Shape ShapeOf() const override { + return children_[active_]->ShapeOf(); + } + + void AppendToPath(iteration::Path& path) const override { + children_[active_]->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + std::vector > children_; + + /** + * Index of the child we currently iterate over + */ + std::size_t active_; + + /** + * Move on to the next children, and start them, until one is not done. + */ + void SkipDoneChildren() { + while (active_ < children_.size() && children_[active_]->Done()) { + ++active_; + if (active_ < children_.size()) { + children_[active_]->Start(); + } + } + } +}; // class ChainIterator + +void CollectChildren( + std::vector >& +) { + // Intentionally empty, as there are no more children to collect. +} + +template +void CollectChildren( + std::vector >& children, + std::unique_ptr first, + Rest... rest +) { + children.emplace_back(std::move(first)); + CollectChildren(children, std::move(rest)...); +} + +// NOTE (mristin): +// We can not use an initializer list here, as we can not move the unique pointers +// out of it. +template +std::unique_ptr Chain( + Children... children +) { + std::vector > collected; + collected.reserve(sizeof...(Children)); + CollectChildren(collected, std::move(children)...); + + return common::make_unique(std::move(collected)); +} + +/** + * Iterate over the values of the \p child, which lives in a property. + */ +class InPropertyIterator : public IIterator { + public: + InPropertyIterator( + iteration::Property property, + std::unique_ptr child + ) : + property_(property), + child_(std::move(child)) { + // Intentionally empty. + } + + InPropertyIterator(const InPropertyIterator& other) : + property_(other.property_), + child_(other.child_->Clone()) { + // Intentionally empty. + } + + void Start() override { + child_->Start(); + } + + void Next() override { + child_->Next(); + } + + bool Done() const override { + return child_->Done(); + } + + const void* Value() const override { + return child_->Value(); + } + + Shape ShapeOf() const override { + return child_->ShapeOf(); + } + + void AppendToPath(iteration::Path& path) const override { + path.segments.emplace_back( + common::make_unique(property_) + ); + child_->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + iteration::Property property_; + std::unique_ptr child_; +}; // class InPropertyIterator + +std::unique_ptr InProperty( + iteration::Property property, + std::unique_ptr child +) { + return common::make_unique(property, std::move(child)); +} + +/** + * \brief Iterate over the values of an instance, dispatched on its runtime type. + * + * Defined below, once all the classes have been covered. + */ +std::unique_ptr DispatchOnModelType( + const types::IClass& instance, + bool recursive +); + +/** + * \brief Iterate recursively over the values of an instance referenced from + * another instance. + * + * We dispatch on the runtime type of the instance only in \ref Start so that + * we descend into the instance only once the iteration reaches it. + */ +class DispatchingIterator : public IIterator { + public: + explicit DispatchingIterator( + const types::IClass* instance + ) : + instance_(instance) { + // Intentionally empty. + } + + DispatchingIterator(const DispatchingIterator& other) : + instance_(other.instance_), + child_(other.child_ == nullptr ? nullptr : other.child_->Clone()) { + // Intentionally empty. + } + + void Start() override { + child_ = DispatchOnModelType(*instance_, true); + child_->Start(); + } + + void Next() override { + child_->Next(); + } + + bool Done() const override { + return child_->Done(); + } + + const void* Value() const override { + return child_->Value(); + } + + Shape ShapeOf() const override { + return child_->ShapeOf(); + } + + void AppendToPath(iteration::Path& path) const override { + child_->AppendToPath(path); + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + const types::IClass* instance_; + std::unique_ptr child_; +}; // class DispatchingIterator + +std::unique_ptr Over( + const types::IClass& instance, + bool recursive +) { + if (!recursive) { + // NOTE (mristin): + // In the non-recursive mode, we verify only the instance itself, but not + // the instances that it references. + return Empty(); + } + + return common::make_unique(&instance); +} + +template +std::unique_ptr ThroughPointer( + const std::shared_ptr& instance, + bool recursive +) { + return Over(*instance, recursive); +} + +std::unique_ptr Over_Container( + const types::IContainer& that, + bool +) { + return One(&that, Shape::kContainer); +} + +std::unique_ptr Over_Wrapper( + const types::IWrapper& that, + bool recursive +) { + return Chain( + One(&that, Shape::kWrapper), + InProperty( + iteration::Property::kContainer, + Over(*that.container(), recursive) + ) + ); +} + +/** + * Iterate over the values of the \p instance, dispatched on its runtime type. + */ +std::unique_ptr DispatchOnModelType( + const types::IClass& instance, + bool recursive +) { + switch (instance.model_type()) { + case types::ModelType::kContainer: + return Over_Container( + dynamic_cast(instance), + recursive + ); + case types::ModelType::kWrapper: + return Over_Wrapper( + dynamic_cast(instance), + recursive + ); + default: + // NOTE (mristin): + // The instances of the other classes have nothing to verify. + return Empty(); + } +} + +// endregion Iteration over the values + +// region Iteration over the errors + +/** + * \brief Iterate over the errors of the values, one error at a time. + * + * For every value, we first run the checks of its shape, each reporting at most + * one error. Then we run the nested verification of the value, if its shape has + * one, which can report many errors, see \ref NewNestedVerificator. + * + * We do only the work needed to find the next error, and build the path to + * the erroneous value only once an error has been found. + */ +class ErrorIterator : public impl::IVerificator { + public: + explicit ErrorIterator( + std::unique_ptr values + ) : + values_(std::move(values)), + check_(0), + nested_started_(false), + index_(-1) { + // Intentionally empty. + } + + ErrorIterator(const ErrorIterator& other) : + values_(other.values_->Clone()), + check_(other.check_), + nested_started_(other.nested_started_), + nested_(other.nested_ == nullptr ? nullptr : other.nested_->Clone()), + error_(other.error_), + index_(other.index_) { + // Intentionally empty. + } + + void Start() override { + values_->Start(); + check_ = 0; + nested_started_ = false; + nested_ = nullptr; + index_ = -1; + + Advance(); + } + + void Next() override { + #ifdef DEBUG + if (Done()) { + throw std::logic_error( + "You want to move an ErrorIterator, but it was done." + ); + } + #endif + + Advance(); + } + + bool Done() const override { + return values_->Done(); + } + + const Error& Get() const override { + #ifdef DEBUG + if (Done()) { + throw std::logic_error( + "You want to get from an ErrorIterator, but it was done." + ); + } + #endif + + return *error_; + } + + Error& GetMutable() override { + #ifdef DEBUG + if (Done()) { + throw std::logic_error( + "You want to get mutable from an ErrorIterator, but it was done." + ); + } + #endif + + return *error_; + } + + long Index() const override { + return index_; + } + + std::unique_ptr Clone() const override { + return common::make_unique(*this); + } + + private: + std::unique_ptr values_; + + /** + * Index of the next check to run on the current value + */ + std::size_t check_; + + /** + * Set if we already started the nested verification of the current value + */ + bool nested_started_; + + /** + * Nested verification of the current value, if its shape has one + */ + std::unique_ptr nested_; + + common::optional error_; + + /** + * Index of the current error, -1 if done + */ + long index_; + + /** + * Move on to the next error, or become done if there are no more errors. + */ + void Advance() { + while (!values_->Done()) { + const void* value = values_->Value(); + const Shape shape = values_->ShapeOf(); + + const std::vector& checks = ChecksOf(shape); + while (check_ < checks.size()) { + const Check& check = checks[check_]; + ++check_; + + if (!check.holds(value)) { + error_ = Error(check.message); + values_->AppendToPath(error_->path); + ++index_; + return; + } + } + + // NOTE (mristin): + // All the checks of the value have been run. We now either start the nested + // verification of the value, or resume it where we stopped at its last error. + if (!nested_started_) { + nested_started_ = true; + nested_ = NewNestedVerificator(shape, value); + if (nested_ != nullptr) { + nested_->Start(); + } + } else if (nested_ != nullptr) { + nested_->Next(); + } + + if (nested_ != nullptr && !nested_->Done()) { + // NOTE (mristin): + // The path of the nested error is relative to the value, so we prefix it + // with the path to the value. We take over the data members of the nested + // error to avoid a costly copy, as we move the nested verification on + // before we look at its error again. + Error& nested_error = nested_->GetMutable(); + + error_ = Error(std::move(nested_error.cause)); + values_->AppendToPath(error_->path); + for ( + std::unique_ptr& segment + : nested_error.path.segments + ) { + error_->path.segments.emplace_back(std::move(segment)); + } + + ++index_; + return; + } + + values_->Next(); + check_ = 0; + nested_started_ = false; + nested_ = nullptr; + } + + error_ = common::nullopt; + index_ = -1; + } +}; // class ErrorIterator + +/** + * Start iterating over the errors of the \p values. + */ +Iterator IterateErrors( + std::unique_ptr values +) { + std::unique_ptr verificator( + common::make_unique(std::move(values)) + ); + verificator->Start(); + + return Iterator(std::move(verificator)); +} + +/** + * Give out the iterator past the last error, shared by all the verifications. + */ +const Iterator& PastLastError() { + static const Iterator iterator(IterateErrors(Empty())); + return iterator; +} + +// endregion Iteration over the errors + +} // namespace + +// region NonRecursiveVerification + +NonRecursiveVerification::NonRecursiveVerification( + const std::shared_ptr& instance +) : instance_(instance) { + // Intentionally empty. +} + +Iterator NonRecursiveVerification::begin() const { + return IterateErrors(DispatchOnModelType(*instance_, false)); +} + +const Iterator& NonRecursiveVerification::end() const { + return PastLastError(); +} + +// endregion NonRecursiveVerification + +// region RecursiveVerification + +RecursiveVerification::RecursiveVerification( + const std::shared_ptr& instance +) : instance_(instance) { + // Intentionally empty. +} + +Iterator RecursiveVerification::begin() const { + return IterateErrors(DispatchOnModelType(*instance_, true)); +} + +const Iterator& RecursiveVerification::end() const { + return PastLastError(); +} + +// endregion RecursiveVerification + +// region struct Iterator + +Iterator::Iterator( + const Iterator& other +) : + verificator_(other.verificator_->Clone()) { + // Intentionally empty. +} + +Iterator::Iterator( + Iterator&& other +) : + verificator_(std::move(other.verificator_)) { + // Intentionally empty. +} + +Iterator& Iterator::operator=(const Iterator& other) { + return *this = Iterator(other); +} + +Iterator& Iterator::operator=(Iterator&& other) { + if (this != &other) { + verificator_ = std::move(other.verificator_); + } + + return *this; +} + +const Error& Iterator::operator*() const { + if (verificator_->Done()) { + throw std::logic_error( + "You want to de-reference from a completed iterator " + "over verification errors." + ); + } + + return verificator_->Get(); +} + +const Error* Iterator::operator->() const { + if (verificator_->Done()) { + throw std::logic_error( + "You want to de-reference from a completed iterator " + "over verification errors." + ); + } + + return &(verificator_->Get()); +} + +// Prefix increment +Iterator& Iterator::operator++() { + if (verificator_->Done()) { + throw std::logic_error( + "You want to move a completed iterator " + "over verification errors." + ); + } + + verificator_->Next(); + return *this; +} + +// Postfix increment +Iterator Iterator::operator++(int) { + Iterator result(*this); + ++(*this); + return result; +} + +bool operator==(const Iterator& a, const Iterator& b) { + return a.verificator_->Index() == b.verificator_->Index(); +} + +bool operator!=(const Iterator& a, const Iterator& b) { + return a.verificator_->Index() != b.verificator_->Index(); +} + +// endregion struct Iterator + +} // namespace verification +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/visitation.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/visitation.cpp new file mode 100644 index 000000000..3735f83cf --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/visitation.cpp @@ -0,0 +1,186 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/types.hpp" +#include "dummy/stringification.hpp" +#include "dummy/visitation.hpp" + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +namespace dummy { +namespace visitation { + +// region AbstractVisitor + +void AbstractVisitor::Visit( + const std::shared_ptr& that +) { + // NOTE (mristin): + // We have to dynamically cast the pointers due to the virtual multiple + // inheritance, and also because we used shared pointers for references. + // If we used constant references instead of shared pointers, we could use + // a pattern such as double dispatch. However, this has the limitation that + // it would prevent us from collecting the instances in the visitor, such that + // they outlive the original structures. + + switch (that->model_type()) { + case types::ModelType::kSomeItem: + VisitSomeItem( + std::dynamic_pointer_cast< + types::ISomeItem + >(that) + ); + break; + case types::ModelType::kSpecialItem: + VisitSpecialItem( + std::dynamic_pointer_cast< + types::ISpecialItem + >(that) + ); + break; + case types::ModelType::kContainer: + VisitContainer( + std::dynamic_pointer_cast< + types::IContainer + >(that) + ); + break; + case types::ModelType::kWrapper: + VisitWrapper( + std::dynamic_pointer_cast< + types::IWrapper + >(that) + ); + break; + default: + throw std::logic_error( + common::Concat( + "Unexpected model type: ", + std::to_string( + static_cast(that->model_type()) + ) + ) + ); + } +} + +// endregion + +namespace { + +// region Pass-through over the containers + +/** + * Pass the \p visitor through the instances held by \p that. + */ +void PassThrough_listOf_Item( + IVisitor* visitor, + const std::vector< + std::shared_ptr + >& that +) { + for ( + const std::shared_ptr& item : + that + ) { + visitor->Visit(item); + } +} + +/** + * Pass the \p visitor through the instances held by \p that. + */ +void PassThrough_listOf_SomeItem( + IVisitor* visitor, + const std::vector< + std::shared_ptr + >& that +) { + for ( + const std::shared_ptr& item : + that + ) { + visitor->Visit(item); + } +} + +/** + * Pass the \p visitor through the instances held by \p that. + */ +void PassThrough_listOf_SpecialItem( + IVisitor* visitor, + const std::vector< + std::shared_ptr + >& that +) { + for ( + const std::shared_ptr& item : + that + ) { + visitor->Visit(item); + } +} + +// endregion Pass-through over the containers + +} // namespace + +// region PassThroughVisitor + +void PassThroughVisitor::VisitSomeItem( + const std::shared_ptr& +) { + // No properties to be passed through. +} + +void PassThroughVisitor::VisitSpecialItem( + const std::shared_ptr& +) { + // No properties to be passed through. +} + +void PassThroughVisitor::VisitContainer( + const std::shared_ptr& that +) { + // mutable_items + PassThrough_listOf_Item(this, that->mutable_items()); + + // mutable_some_items + PassThrough_listOf_SomeItem( + this, + that->mutable_some_items() + ); + + // region mutable_special_items + const common::optional< + std::vector< + std::shared_ptr + > + >& maybe_special_items( + that->mutable_special_items() + ); + if (maybe_special_items.has_value()) { + PassThrough_listOf_SpecialItem( + this, + maybe_special_items.value() + ); + } + // endregion +} + +void PassThroughVisitor::VisitWrapper( + const std::shared_ptr& that +) { + // mutable_container + this->Visit(that->mutable_container()); +} + +// endregion + +} // namespace visitation +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/wstringification.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/wstringification.cpp new file mode 100644 index 000000000..eef0b37f5 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/wstringification.cpp @@ -0,0 +1,94 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/wstringification.hpp" + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +namespace dummy { +namespace wstringification { + +const std::unordered_map< + std::wstring, + types::ModelType +> kModelTypeFromWstringMap = { + { + L"SomeItem", + types::ModelType::kSomeItem + }, + { + L"SpecialItem", + types::ModelType::kSpecialItem + }, + { + L"Container", + types::ModelType::kContainer + }, + { + L"Wrapper", + types::ModelType::kWrapper + } +}; + +common::optional ModelTypeFromWstring( + const std::wstring& text +) { + const auto it = kModelTypeFromWstringMap.find( + text + ); + if (it == kModelTypeFromWstringMap.end()) { + return {}; + } + return it->second; +} + +types::ModelType MustModelTypeFromWstring( + const std::wstring& text +) { + const auto it = kModelTypeFromWstringMap.find( + text + ); + if (it == kModelTypeFromWstringMap.end()) { + throw std::invalid_argument( + common::WstringToUtf8( + common::Concat( + L"Unexpected ModelType literal: ", + text + ) + ) + ); + } + return it->second; +} + +std::wstring to_wstring( + types::ModelType model_type +) { + switch (model_type) { + case types::ModelType::kSomeItem: + return L"SomeItem"; + case types::ModelType::kSpecialItem: + return L"SpecialItem"; + case types::ModelType::kContainer: + return L"Container"; + case types::ModelType::kWrapper: + return L"Wrapper"; + default: + throw std::invalid_argument( + common::Concat( + "Unexpected ModelType literal: ", + std::to_string( + static_cast(model_type) + ) + ) + ); + } +} + +} // namespace wstringification +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.cpp new file mode 100644 index 000000000..76d2b8e59 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.cpp @@ -0,0 +1,1624 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "xml_common.hpp" + +#include "dummy/stringification.hpp" + +#pragma warning(push, 0) +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +static_assert( + !std::is_same::value, + "Expected Expat to be compiled with UTF-8 support, i.e., that character is " + "stored as char internally, " + "but Expat was compiled to store characters internally as UTF-16." +); + +static_assert( + std::is_same::value, + "Expected Expat to be compiled with UTF-8 support, i.e., that " + "character is stored as char internally, " + "but it was not." +); + +namespace dummy { +namespace xml_common { + +const char* const kNamespace = "https://dummy.com"; + +// region Node kind + +const std::unordered_map< + NodeKind, + std::string +> kNodeKindToHumanReadableString = { + {NodeKind::Bof, "a beginning-of-input"}, + {NodeKind::Start, "a start element"}, + {NodeKind::Stop, "a stop element"}, + {NodeKind::Text, "a text"}, + {NodeKind::Eof, "an end-of-input"}, + {NodeKind::Error, "an error"}, + {NodeKind::StartInNoNamespace, "a start element in no namespace"}, + {NodeKind::StopInNoNamespace, "a stop element in no namespace"}, +}; + +const std::string& NodeKindToHumanReadableString(NodeKind kind) { + auto it = kNodeKindToHumanReadableString.find(kind); + if (it == kNodeKindToHumanReadableString.end()) { + throw std::invalid_argument( + common::Concat( + "Unexpected node kind: ", + std::to_string( + static_cast(kind) + ) + ) + ); + } + + return it->second; +} + +// endregion Node kind + +std::string NodeToHumanReadableString( + const INode& node +) { + switch (node.kind()) { + case NodeKind::Bof: + return "beginning-of-input"; + + case NodeKind::Start: + case NodeKind::StartInNoNamespace: { + const StartNode& start_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StartNode& + >(node) + ); + + return common::Concat( + "a start node <", + start_node.name, + ">" + ); + } + + case NodeKind::Stop: + case NodeKind::StopInNoNamespace: { + const StopNode& stop_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StopNode& + >(node) + ); + + return common::Concat( + "a stop node " + ); + } + + case NodeKind::Text: + return "an XML text"; + + case NodeKind::Eof: + return "end-of-input"; + + case NodeKind::Error: + return "an XML error"; + + default: + throw std::invalid_argument( + common::Concat( + "Unexpected node kind: ", + std::to_string( + static_cast(node.kind()) + ) + ) + ); + } +} + +std::wstring NodeToHumanReadableWstring( + const INode& node +) { + switch (node.kind()) { + case NodeKind::Bof: + return L"beginning-of-input"; + + case NodeKind::Start: + case NodeKind::StartInNoNamespace: { + const StartNode& start_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StartNode& + >(node) + ); + + return common::Concat( + L"a start node <", + common::Utf8ToWstring(start_node.name), + L">" + ); + } + + case NodeKind::Stop: + case NodeKind::StopInNoNamespace: { + const StopNode& stop_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StopNode& + >(node) + ); + + return common::Concat( + L"a stop node " + ); + } + + case NodeKind::Text: + return L"an XML text"; + + case NodeKind::Eof: + return L"end-of-input"; + + case NodeKind::Error: + return L"an XML error"; + + default: + throw std::invalid_argument( + common::Concat( + "Unexpected node kind: ", + std::to_string( + static_cast(node.kind()) + ) + ) + ); + } +} + +bool IsStopNodeWithName( + const INode& node, + const std::string& expected_name +) { + if (node.kind() != NodeKind::Stop) { + return false; + } + + const std::string& name( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StopNode& + >(node).name + ); + if (name != expected_name) { + return false; + } + + return true; +} + +bool IsStopNodeWithNameInNoNamespace( + const INode& node, + const std::string& expected_name +) { + if (node.kind() != NodeKind::StopInNoNamespace) { + return false; + } + + const std::string& name( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const StopNode& + >(node).name + ); + if (name != expected_name) { + return false; + } + + return true; +} + +// region XSD lexical forms + +std::string CollapseWhitespace(const std::string& text) { + std::string result; + result.reserve(text.size()); + + bool pending_space = false; + for (const char character : text) { + if ( + character == ' ' + || character == '\t' + || character == '\n' + || character == '\r' + ) { + // NOTE (mristin): + // A space is only worth keeping once something has come before it, + // which trims the leading ones, and it is written out only when + // something follows, which trims the trailing ones. + pending_space = !result.empty(); + } else { + if (pending_space) { + result += ' '; + pending_space = false; + } + result += character; + } + } + + return result; +} + +bool MatchesXsLongNumeral(const std::string& text) { + std::size_t cursor = 0; + const std::size_t size = text.size(); + + if (cursor < size && (text[cursor] == '+' || text[cursor] == '-')) { + ++cursor; + } + + std::size_t digits = 0; + while (cursor < size && text[cursor] >= '0' && text[cursor] <= '9') { + ++cursor; + ++digits; + } + + return digits > 0 && cursor == size; +} + +bool MatchesXsDoubleNumeral(const std::string& text) { + std::size_t cursor = 0; + const std::size_t size = text.size(); + + if (cursor < size && (text[cursor] == '+' || text[cursor] == '-')) { + ++cursor; + } + + std::size_t digits_before_the_point = 0; + while (cursor < size && text[cursor] >= '0' && text[cursor] <= '9') { + ++cursor; + ++digits_before_the_point; + } + + std::size_t digits_after_the_point = 0; + if (cursor < size && text[cursor] == '.') { + ++cursor; + while (cursor < size && text[cursor] >= '0' && text[cursor] <= '9') { + ++cursor; + ++digits_after_the_point; + } + } + + // NOTE (mristin): + // A numeral needs a digit somewhere, but on either side of the point will + // do: both "1." and ".5" are good xs:double numerals. + if (digits_before_the_point == 0 && digits_after_the_point == 0) { + return false; + } + + if (cursor < size && (text[cursor] == 'e' || text[cursor] == 'E')) { + ++cursor; + + if (cursor < size && (text[cursor] == '+' || text[cursor] == '-')) { + ++cursor; + } + + std::size_t digits_in_the_exponent = 0; + while (cursor < size && text[cursor] >= '0' && text[cursor] <= '9') { + ++cursor; + ++digits_in_the_exponent; + } + + if (digits_in_the_exponent == 0) { + return false; + } + } + + return cursor == size; +} + +bool MatchesXsBase64Binary(const std::string& text) { + if (text.size() % 4 != 0) { + return false; + } + + if (text.empty()) { + return true; + } + + std::size_t pads = 0; + if (text[text.size() - 1] == '=') { + pads = 1; + if (text[text.size() - 2] == '=') { + pads = 2; + } + } + + for (std::size_t i = 0; i < text.size() - pads; ++i) { + const char character = text[i]; + const bool in_alphabet( + (character >= 'A' && character <= 'Z') + || (character >= 'a' && character <= 'z') + || (character >= '0' && character <= '9') + || character == '+' + || character == '/' + ); + if (!in_alphabet) { + return false; + } + } + + // NOTE (mristin): + // Only these sixteen characters leave the two dropped bits at zero, and + // only these four leave the four dropped bits at zero. + if (pads == 1) { + return std::string("AEIMQUYcgkosw048").find(text[text.size() - 2]) + != std::string::npos; + } + + if (pads == 2) { + return std::string("AQgw").find(text[text.size() - 3]) + != std::string::npos; + } + + return true; +} + +std::string RemoveWhitespace(const std::string& text) { + std::string result; + result.reserve(text.size()); + + for (const char character : text) { + if ( + character != ' ' + && character != '\t' + && character != '\n' + && character != '\r' + ) { + result += character; + } + } + + return result; +} + +// endregion XSD lexical forms + +// region class Reader + +/** + * Structure the data passed over to Expat XML reader. + */ +struct OurData { + bool additional_attributes; + size_t buffer_size; + XML_Parser parser; + + std::deque >& node_buffer; + + bool stopped = false; + + OurData( + bool the_additional_attributes, + size_t a_buffer_size, + XML_Parser a_parser, + std::deque >& a_node_buffer + ) : + additional_attributes(the_additional_attributes), + buffer_size(a_buffer_size), + parser(a_parser), + node_buffer(a_node_buffer) { + // Intentionally empty. + } +}; // struct OurData + +Reader::Reader( + std::istream& is, + bool additional_attributes, + size_t buffer_size +) : + additional_attributes_(additional_attributes), + buffer_size_(buffer_size), + is_(is), + parser_(nullptr), + current_(common::make_unique()), + eof_(false), + error_(false) { + // Intentionally empty. +} + +const char kNamespaceSeparator = '|'; + +void XMLCALL OnStartElement( + void* user_data, + const char* name, + const char* attributes[] +) { + auto our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->stopped) { + return; + } + + const std::string name_str(name); + + // NOTE (mristin): + // Expat spells a namespaced element as `namespace|local`, and an element + // in no namespace as the bare local name. We accept the both: the elements + // of the document reside in \ref kNamespace, and the elements of + // the XML-RPC subset in no namespace at all. Which of the two is expected + // where is decided by the caller, as it depends on the grammar and not on + // the document. + size_t separator_i = name_str.find(kNamespaceSeparator); + + const bool in_namespace = separator_i != std::string::npos; + + if (separator_i == 0) { + std::string message = common::Concat( + "The namespace is empty in the start element <", + name_str, + ">" + ); + + our_data->node_buffer.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(our_data->parser), + XML_GetCurrentColumnNumber(our_data->parser), + message + ) + ); + + XML_StopParser(our_data->parser, false); + our_data->stopped = true; + return; + } + + const std::string local_name( + in_namespace + ? name_str.substr(separator_i + 1) + : name_str + ); + + if ( + in_namespace + && name_str.compare(0, separator_i, kNamespace) != 0 + ) { + std::string message = common::Concat( + "We expected the XML namespace ", + kNamespace, + ", or no namespace at all, but we got the namespace ", + name_str.substr(0, separator_i), + " in the start element <", + local_name, + ">" + ); + + our_data->node_buffer.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(our_data->parser), + XML_GetCurrentColumnNumber(our_data->parser), + message + ) + ); + + XML_StopParser(our_data->parser, false); + our_data->stopped = true; + return; + } + + if ( + attributes[0] != nullptr + && !(our_data->additional_attributes) + ) { + std::string message = common::Concat( + "Additional attributes are not allowed, " + "but the attribute ", + attributes[0], + " was read in the start element <", + local_name, + ">" + ); + + our_data->node_buffer.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(our_data->parser), + XML_GetCurrentColumnNumber(our_data->parser), + message + ) + ); + + XML_StopParser(our_data->parser, false); + our_data->stopped = true; + return; + } + + our_data->node_buffer.emplace_back( + common::make_unique( + local_name, + in_namespace + ) + ); +} + +void XMLCALL OnStopElement( + void* user_data, + const char* name +) { + auto* our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->stopped) { + return; + } + + const std::string name_str(name); + + size_t separator_i = name_str.find(kNamespaceSeparator); + + const bool in_namespace = separator_i != std::string::npos; + + if (separator_i == 0) { + std::string message = common::Concat( + "The namespace is empty in the stop element " + ); + + our_data->node_buffer.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(our_data->parser), + XML_GetCurrentColumnNumber(our_data->parser), + message + ) + ); + + XML_StopParser(our_data->parser, false); + our_data->stopped = true; + return; + } + + const std::string local_name( + in_namespace + ? name_str.substr(separator_i + 1) + : name_str + ); + + if ( + in_namespace + && name_str.compare(0, separator_i, kNamespace) != 0 + ) { + std::string message = common::Concat( + "We expected the XML namespace ", + kNamespace, + ", or no namespace at all, but we got the namespace ", + name_str.substr(0, separator_i), + " in the stop element " + ); + + our_data->node_buffer.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(our_data->parser), + XML_GetCurrentColumnNumber(our_data->parser), + message + ) + ); + + XML_StopParser(our_data->parser, false); + our_data->stopped = true; + return; + } + + our_data->node_buffer.emplace_back( + common::make_unique( + local_name, + in_namespace + ) + ); +} + +void XMLCALL OnText( + void* user_data, + const char* val, + int len +) { + auto our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->stopped) { + return; + } + + our_data->node_buffer.emplace_back( + common::make_unique( + std::string(val, len) + ) + ); +} + +void Reader::Initialize() { + // NOTE (mristin): + // We set up the underlying parser here instead of the constructor + // to avoid throwing exceptions in the constructor. + + if (parser_ != nullptr) { + throw std::logic_error( + "You are trying to re-initialize an initialized XML reader." + ); + } + + if ( + buffer_size_ + > static_cast( + (std::numeric_limits::max)() + ) + ) { + throw std::invalid_argument( + common::Concat( + "Expat library expects the buffer size as int, " + "but the given buffer size ", + std::to_string(buffer_size_), + " does not fit in an int as it is larger than the maximum int ", + std::to_string(std::numeric_limits::max()) + ) + ); + } + + parser_ = XML_ParserCreateNS(nullptr, kNamespaceSeparator); + our_data_ = common::make_unique( + additional_attributes_, + buffer_size_, + parser_, + node_buffer_ + ); + + XML_SetUserData(parser_, our_data_.get()); + XML_SetElementHandler( + parser_, + OnStartElement, + OnStopElement + ); + XML_SetCharacterDataHandler(parser_, OnText); + + chunk_.resize(buffer_size_); + + Read(); +} + +void Reader::Read() { + if (parser_ == nullptr) { + throw std::logic_error( + "You are trying to read from an uninitialized XML reader" + ); + } + + if (eof_) { + throw std::logic_error( + "The XML reader reached the end-of-input, " + "but you called Read()" + ); + } + + if (error_) { + throw std::logic_error( + "There was an error while reading XML, " + "but you called Read() again" + ); + } + + while (node_buffer_.empty()) { + // NOTE (mristin): + // We read and parse the next chunk of input, until we parsed a whole node. + // The text, however, will be fragmented by Expat's design. + + is_.read(&(chunk_[0]), buffer_size_); + + const std::streamsize actual_bytes_read = is_.gcount(); + + if (is_.bad()) { + SetCurrentAndEofAndError( + common::make_unique( + 0, + 0, + "Failed to read from the input" + ) + ); + return; + } + + if (actual_bytes_read == 0) { + if (is_.eof()) { + SetCurrentAndEofAndError(common::make_unique()); + return; + } else { + SetCurrentAndEofAndError( + common::make_unique( + 0, + 0, + "Read zero bytes from the input, " + "but the input is neither eof() nor bad()" + ) + ); + return; + } + } else { + const bool done = is_.eof(); + + if (actual_bytes_read > std::numeric_limits::max()) { + std::string message = common::Concat( + "Expat library expects the buffer size as int, ", + "but the actual number of bytes read ", + std::to_string(actual_bytes_read), + " does not fit in an int as it is larger than the maximum int ", + std::to_string(std::numeric_limits::max()) + ); + + throw std::runtime_error(message); + } + + const auto actual_bytes_read_int = static_cast(actual_bytes_read); + + XML_Status status = XML_Parse( + parser_, + &(chunk_[0]), + actual_bytes_read_int, + done + ); + + if (status == XML_STATUS_ERROR) { + XML_Error error_code = XML_GetErrorCode(parser_); + + if (error_code == XML_ERROR_ABORTED) { + if (node_buffer_.empty()) { + throw std::logic_error( + "The XML parsing was aborted, " + "so we expected an error node on the buffer, " + "but the buffer was empty" + ); + } + + if (node_buffer_.back()->kind() != NodeKind::Error) { + std::string message = common::Concat( + "The XML parsing was aborted, " + "so we expected an error node on the buffer, " + "but we got ", + NodeKindToHumanReadableString(node_buffer_.back()->kind()) + ); + + throw std::logic_error(message); + } + } else { + const XML_LChar* error_str = XML_ErrorString(error_code); + + node_buffer_.emplace_back( + common::make_unique( + XML_GetCurrentLineNumber(parser_), + XML_GetCurrentColumnNumber(parser_), + std::string(error_str) + ) + ); + } + } else { + if (done) { + node_buffer_.emplace_back(common::make_unique()); + } + } + } + } + + SetCurrentAndEofAndError(std::move(node_buffer_.front())); + node_buffer_.pop_front(); +} + +const INode& Reader::node() const { + return *current_; +} + +std::unique_ptr Reader::moved_node() { + return std::move(current_); +} + +Reader::~Reader() { + if (parser_ != nullptr) { + XML_ParserFree(parser_); + } +} + +void Reader::SetCurrentAndEofAndError( + std::unique_ptr node +) { + current_ = std::move(node); + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wswitch" + #endif + switch (current_->kind()) { + case NodeKind::Eof: + eof_ = true; + break; + case NodeKind::Error: + error_ = true; + break; + } + #ifdef __clang__ + #pragma clang diagnostic pop + #endif +} + +// endregion class Reader + +// region class ReaderMergingText + +ReaderMergingText::ReaderMergingText( + std::istream& is, + bool additional_attributes, + size_t buffer_size +) : + initialized_(false), + reader_(is, additional_attributes, buffer_size), + current_(common::make_unique()), + error_(false), + eof_(false) { + // Intentionally empty. +} + +void ReaderMergingText::Initialize() { + if (initialized_) { + throw std::logic_error( + "You are trying to initialize " + "an already initialized ReaderMergingText" + ); + } + + reader_.Initialize(); + + // NOTE (mristin): + // The `reader_` has already read a node. Hence, we need to parse it here + // separately from `ReaderMergingText::Read` method. + + if (reader_.node().kind() != NodeKind::Text) { + SetCurrentAndEofAndError(reader_.moved_node()); + } else { + ReadAndMergeAllTextAndSetLookahead(); + } + + initialized_ = true; +} + +void ReaderMergingText::Read() { + if (!initialized_) { + throw std::logic_error( + "You are reading from an uninitialized ReaderMergingText" + ); + } + + if (eof()) { + throw std::logic_error( + "You are trying to read from a ReaderMergingText, " + "but it reached the end-of-input" + ); + } + + if (error()) { + throw std::logic_error( + "You are trying to read from a ReaderMergingText, " + "but an error already occurred" + ); + } + + if (look_ahead_ != nullptr) { + SetCurrentAndEofAndError(std::move(look_ahead_)); + return; + } + + reader_.Read(); + if (reader_.node().kind() != NodeKind::Text) { + SetCurrentAndEofAndError(reader_.moved_node()); + } else { + ReadAndMergeAllTextAndSetLookahead(); + } +} + +const INode& ReaderMergingText::node() const { + return *current_; +} + +bool ReaderMergingText::error() const { + return error_; +} + +bool ReaderMergingText::eof() const { + return eof_; +} + +void ReaderMergingText::ReadAndMergeAllTextAndSetLookahead() { + if (reader_.node().kind() != NodeKind::Text) { + std::string message = common::Concat( + "Expected the current node in the reader " + "underlying ReaderMergingText to be a text, " + "but it was ", + NodeKindToHumanReadableString(reader_.node().kind()) + ); + + throw std::logic_error(message); + } + + std::deque text_buffer; + + while (reader_.node().kind() == NodeKind::Text) { + const TextNode& fragment_text_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const TextNode& + >( + reader_.node() + ) + ); + + text_buffer.emplace_back(fragment_text_node.text); + + reader_.Read(); + } + look_ahead_ = reader_.moved_node(); + + size_t size = 0; + for (const std::string& fragment : text_buffer) { + size += fragment.size(); + } + + std::string text; + text.reserve(size); + while (!text_buffer.empty()) { + text.append(text_buffer.front()); + text_buffer.pop_front(); + } + + SetCurrentAndEofAndError(common::make_unique(text)); +} + +void ReaderMergingText::SetCurrentAndEofAndError(std::unique_ptr node) { + current_ = std::move(node); + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wswitch" + #endif + switch (current_->kind()) { + case NodeKind::Eof:eof_ = true; + break; + case NodeKind::Error:error_ = true; + break; + } + #ifdef __clang__ + #pragma clang diagnostic pop + #endif +} + +// endregion class ReaderMergingText + +SerializationError::SerializationError( + std::wstring a_cause +) : + cause(std::move(a_cause)) { + // Intentionally empty. +} + +const std::wstring kTheOutputStreamIsInABadState( + L"The output stream is in a bad state." +); + +common::optional CheckOstreamState( + const std::ostream& os +) { + if (os.bad()) { + return common::make_optional( + kTheOutputStreamIsInABadState + ); + } + + return common::nullopt; +} + +// region class SelfClosingWriter + +SelfClosingWriter::SelfClosingWriter( + std::ostream& os, + const char* root_attributes +) : + os_(os), + root_attributes_(root_attributes), + pending_attributes_("") { + // Intentionally empty. +} + +void SelfClosingWriter::StartElement( + std::string name +) { + #ifdef DEBUG + if (error_.has_value()) { + throw std::logic_error( + "You are trying to queue a start element with a SelfClosingWriter " + "which caught an error." + ); + } + #endif + + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + pending_start_wo_text_ = std::move(name); + + // NOTE (mristin): + // The outermost element carries the attributes of the root, and every other + // element carries none. We take them here instead of asking whether this is + // the first element, since the assignment has to happen either way. + pending_attributes_ = root_attributes_; + root_attributes_ = ""; +} + +void SelfClosingWriter::StopElement( + const std::string& name +) { + #ifdef DEBUG + if (error_.has_value()) { + throw std::logic_error( + "You are trying to write a stop element with a SelfClosingWriter " + "which caught an error before." + ); + } + #endif + + if (pending_start_wo_text_.has_value()) { + #ifdef DEBUG + if (*pending_start_wo_text_ != name) { + throw std::logic_error( + common::Concat( + "The start element <", + *pending_start_wo_text_, + "> is pending for writing, " + "but you are trying to write a stop element " + ) + ); + } + #endif + + pending_start_wo_text_ = common::nullopt; + + WriteStringWithoutEscapingNorFlushing( + common::Concat( + "<", + name, + pending_attributes_, + "/>" + ) + ); + } else { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + WriteStringWithoutEscapingNorFlushing( + common::Concat( + "" + ) + ); + } +} + +void SelfClosingWriter::SerializeBool( + bool value +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + WriteStringWithoutEscapingNorFlushing( + value ? "true" : "false" + ); +} + +void SelfClosingWriter::SerializeInt64( + int64_t value +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + WriteStringWithoutEscapingNorFlushing( + std::to_string(value) + ); +} + +void SelfClosingWriter::SerializeDouble( + double value +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + // NOTE (mristin): + // We handle edge values infinity and not-a-number explicitly here + // as some C/C++ implementations might not convert them to XML-conformant + // strings. + + if (std::isinf(value)) { + if (value < 0) { + WriteStringWithoutEscapingNorFlushing("-INF"); + } else { + WriteStringWithoutEscapingNorFlushing("INF"); + } + } else if(std::isnan(value)) { + WriteStringWithoutEscapingNorFlushing("NaN"); + } else { + // NOTE (mristin): + // We have to use a stream to avoid trailing zeros. std::to_string always + // outputs trailing zeros up to a certain number of decimal places (usually 6). + // + // A stream left at its default precision is no good either: that is six + // *significant* digits, so 1.2345678901234567 would go out as 1.23457, and + // the value could never be read back. We have to write as many digits as it + // takes for the text to read back as the very same double, and no more. + // + // std::to_chars would give us exactly that in one call, but it is C++17, + // and the library has to compile as C++11. Seventeen significant digits + // always suffice, and fewer usually do, so the candidates are tried in + // turn and the first one which round-trips is kept. The stream strips + // the trailing zeros of each, so a short value stays short. + // + // The classic locale is imposed on both streams: a global locale with + // a decimal comma would otherwise write 1,2345, which no XML parser reads + // back as a number. + std::ostringstream oss; + oss.imbue(std::locale::classic()); + + for (int precision = 15; precision <= 17; ++precision) { + oss.str(""); + oss.clear(); + oss << std::setprecision(precision) << value; + + std::istringstream iss(oss.str()); + iss.imbue(std::locale::classic()); + double round_tripped = 0.0; + iss >> round_tripped; + + // NOTE (mristin): + // The failbit has to be consulted, and not only the value. On overflow + // the stream stores the largest representable double and *fails*, so + // comparing the value alone would accept a text which every correctly + // rounding parser reads as an infinity: 15 significant digits of + // the largest double give 1.79769313486232e+308, which lies above + // the overflow threshold. + if (!iss.fail() && round_tripped == value) { + break; + } + } + + WriteStringWithoutEscapingNorFlushing( + oss.str() + ); + } +} + +void SelfClosingWriter::SerializeString( + const std::string& text +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + // NOTE (mristin): + // We optimize here for short and long texts, respectively. + // The main assumption is that the short texts can be escaped and converted + // in one go, while the longer texts need to be converted in chunks. + + if (text.size() < 1024) { + common::optional escaped = EscapeForXml(text); + + if (escaped.has_value()) { + WriteStringWithoutEscapingNorFlushing( + *escaped + ); + return; + } else { + WriteStringWithoutEscapingNorFlushing( + text + ); + return; + } + } + + size_t start = 0; + while (start < text.size()) { + const size_t end = std::min(start + 1024, text.size()); + const size_t chunk_size = end - start; + + // NOTE (mristin): + // We assume that making short copies of text substrings does not hurt + // the performance here, but makes the code more readable. + + const std::string chunk = text.substr(start, chunk_size); + + common::optional escaped = EscapeForXml(chunk); + + if (escaped.has_value()) { + WriteStringWithoutEscapingNorFlushing( + *escaped + ); + } else { + WriteStringWithoutEscapingNorFlushing( + chunk + ); + } + + if (error_.has_value()) { + return; + } + + start += chunk_size; + } +} + +void SelfClosingWriter::SerializeWstring( + const std::wstring& text +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + // NOTE (mristin): + // We optimize here for short and long texts, respectively. + // The main assumption is that the short texts can be escaped and converted + // in one go, while the longer texts need to be converted in chunks. + + if (text.size() < 1024) { + common::optional escaped = EscapeForXml(text); + + if (escaped.has_value()) { + WriteStringWithoutEscapingNorFlushing( + common::WstringToUtf8(*escaped) + ); + return; + } else { + WriteStringWithoutEscapingNorFlushing( + common::WstringToUtf8(text) + ); + return; + } + } + + size_t start = 0; + while (start < text.size()) { + const size_t end = std::min(start + 1024, text.size()); + const size_t chunk_size = end - start; + + // NOTE (mristin): + // We assume that making short copies of text substrings does not hurt + // the performance here, but makes the code more readable. + + const std::wstring chunk = text.substr(start, chunk_size); + + common::optional escaped = EscapeForXml(chunk); + + if (escaped.has_value()) { + WriteStringWithoutEscapingNorFlushing( + common::WstringToUtf8(*escaped) + ); + } else { + WriteStringWithoutEscapingNorFlushing( + common::WstringToUtf8(chunk) + ); + } + + if (error_.has_value()) { + return; + } + + start += chunk_size; + } +} + +void SelfClosingWriter::SerializeByteArray( + const std::vector& byte_array +) { + WritePendingStartElementIfAvailable(); + if (error_.has_value()) { + return; + } + + // NOTE (mristin): + // We optimize here for short and long byte arrays, respectively. + // The main assumption is that the short texts can be escaped and converted + // in one go, while the longer texts need to be converted in chunks. + // + // Optimally, we would write an encoding function such that it encodes directly + // to the output stream. As we lack the time resources for that at the moment, + // we go for the compromise with one pass and chunking, respectively. + + if (byte_array.size() <= 1536) { + WriteStringWithoutEscapingNorFlushing( + stringification::Base64Encode(byte_array) + ); + return; + } + + // NOTE (mristin): + // We assume here that making copies of small sub-arrays does not hurt + // the performance, but makes the code substantially more readable. + + size_t start = 0; + while (start < byte_array.size()) { + // NOTE (mristin): + // We pick a multiple of 3 for the chunk size in order to make the encoding + // of chunking identical to the output as we encoded all bytes at the same time. + // + // See: https://stackoverflow.com/questions/7920780/is-it-possible-to-base64-encode-a-file-in-chunks + + const size_t end = std::min(start + 1536, byte_array.size()); + + const std::vector chunk( + byte_array.begin() + start, + byte_array.begin() + end + ); + + WriteStringWithoutEscapingNorFlushing( + stringification::Base64Encode(chunk) + ); + if (error_.has_value()) { + return; + } + } +} + +void SelfClosingWriter::Finish() { + WritePendingStartElementIfAvailable(); +} + +const common::optional& SelfClosingWriter::error() const { + return error_; +} + +common::optional&& SelfClosingWriter::move_error() { + return std::move(error_); +} + +common::optional SelfClosingWriter::EscapeForXml( + const std::wstring& text +) { + size_t out_len = 0; + + // NOTE (mristin): + // We use sizeof on *strings* instead of *wide strings* to get + // the number of *characters*. Otherwise, if we used wide strings, + // we would obtain the wrong number of characters as we would count + // bytes instead of characters with `sizeof`, which differ in wide strings + // due to encoding. + + for (wchar_t character : text ) { + switch (character) { + case L'&': { + out_len += sizeof("&"); + break; + } + case L'<': { + out_len += sizeof("<"); + break; + } + case L'>': { + out_len += sizeof(">"); + break; + } + case L'"': { + out_len += sizeof("""); + break; + } + case L'\'': { + out_len += sizeof("'"); + break; + } + default: + ++out_len; + break; + } + } + + // NOTE (mristin): + // We assume here that XML encoding is always *longer* than + // the original text. + + if (out_len == text.size()) { + return common::nullopt; + } + + std::wstring out; + out.reserve(out_len); + + for (wchar_t character : text ) { + switch (character) { + case L'&': + out.append(L"&"); + break; + case L'<': + out.append(L"<"); + break; + case L'>': + out.append(L">"); + break; + case L'"': + out.append(L"""); + break; + case L'\'': + out.append(L"'"); + break; + default: + out.push_back(character); + break; + } + } + + return common::make_optional( + std::move(out) + ); +} + +common::optional SelfClosingWriter::EscapeForXml( + const std::string& text +) { + size_t out_len = 0; + + for (char character : text ) { + switch (character) { + case '&': { + out_len += sizeof("&"); + break; + } + case '<': { + out_len += sizeof("<"); + break; + } + case '>': { + out_len += sizeof(">"); + break; + } + case '"': { + out_len += sizeof("""); + break; + } + case '\'': { + out_len += sizeof("'"); + break; + } + default: + ++out_len; + break; + } + } + + // NOTE (mristin): + // We assume here that XML encoding is always *longer* than + // the original text. + + if (out_len == text.size()) { + return common::nullopt; + } + + std::string out; + out.reserve(out_len); + + for (char character : text ) { + switch (character) { + case '&': + out.append("&"); + break; + case '<': + out.append("<"); + break; + case '>': + out.append(">"); + break; + case '"': + out.append("""); + break; + case '\'': + out.append("'"); + break; + default: + out.push_back(character); + break; + } + } + + return common::make_optional( + std::move(out) + ); +} + +void SelfClosingWriter::WritePendingStartElementIfAvailable() { + if (!pending_start_wo_text_.has_value()) { + return; + } + + WriteStringWithoutEscapingNorFlushing( + common::Concat( + "<", + *pending_start_wo_text_, + pending_attributes_, + ">" + ) + ); + + pending_start_wo_text_ = common::nullopt; +} + +void SelfClosingWriter::WriteStringWithoutEscapingNorFlushing( + const std::string& text +) { + #ifdef DEBUG + if (error_.has_value()) { + throw std::logic_error( + "You are trying to write to a SelfClosingWriter which " + "caught an error" + ); + } + #endif + + if (os_.bad()) { + error_ = common::make_optional( + kTheOutputStreamIsInABadState + ); + return; + } + + os_ << text; + + if (os_.bad()) { + error_ = common::make_optional( + kTheOutputStreamIsInABadState + ); + return; + } +} + +// endregion class SelfClosingWriter + +} // namespace xml_common +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.hpp new file mode 100644 index 000000000..f74c5007b --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_common.hpp @@ -0,0 +1,676 @@ +#ifndef DUMMY_XML_COMMON_GUARD_ +#define DUMMY_XML_COMMON_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/common.hpp" +#include "dummy/iteration.hpp" + +#pragma warning(push, 0) +#include + +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { + +namespace xml_common { + +/** + * \brief The XML namespace in which all the elements of a document reside. + * + * The elements of the XML-RPC subset, over which a JSON-able value is + * de/serialized, are the one exception: they reside in no namespace at all. + */ +extern const char* const kNamespace; + +// region Node kind + +enum class NodeKind : std::uint32_t { + // Nodes of the kind `Bof` represent the beginning-of-input, before any read. + Bof = 0, + // Nodes of the kind `Start` and `Stop` represent an element which resides in + // \ref kNamespace. + Start = 1, + Stop = 2, + Text = 3, + // Nodes of the kind `Eof` represent the end-of-input. + Eof = 4, + // Nodes of the kind `Error` represent low-level errors in the XML parsing. + Error = 5, + // Nodes of the kind `StartInNoNamespace` and `StopInNoNamespace` represent + // an element which resides in no namespace at all. Only the XML-RPC subset, + // over which a JSON-able value is de/serialized, is written like that. + StartInNoNamespace = 6, + StopInNoNamespace = 7 +}; // enum class NodeKind + +extern const std::unordered_map< + NodeKind, + std::string +> kNodeKindToHumanReadableString; + +const std::string& NodeKindToHumanReadableString(NodeKind kind); + +// endregion Node kind + +// region Nodes + +/** + * Model a node in an XML document. + */ +class INode { + public: + /** + * @return the kind of the node, used instead of much slower RTTI. + */ + virtual NodeKind kind() const = 0; + virtual ~INode() = default; +}; // class INode + +/** + * Model the beginning of the input, before anything was read. + */ +class BofNode : public INode { + public: + NodeKind kind() const override { return NodeKind::Bof; } + + ~BofNode() override = default; +}; // class StartNode + +/** + * Model a start of an XML element. + */ +class StartNode : public INode { + public: + StartNode( + std::string a_name, + bool an_in_namespace + ) : + name(std::move(a_name)), + in_namespace(an_in_namespace) { + // Intentionally empty. + } + + NodeKind kind() const override { + return in_namespace + ? NodeKind::Start + : NodeKind::StartInNoNamespace; + } + + /** + * Name of the start element, stripped of the XML namespace + */ + const std::string name; + + /** + * True if the element resides in \ref kNamespace, and false if it resides + * in no namespace at all + */ + const bool in_namespace; + + ~StartNode() override = default; +}; // class StartNode + +/** + * Model a stop of an XML element. + */ +class StopNode : public INode { + public: + StopNode( + std::string a_name, + bool an_in_namespace + ) : + name(std::move(a_name)), + in_namespace(an_in_namespace) { + // Intentionally empty. + } + + NodeKind kind() const override { + return in_namespace + ? NodeKind::Stop + : NodeKind::StopInNoNamespace; + } + + /** + * Name of the stop element, stripped of the XML namespace + */ + const std::string name; + + /** + * True if the element resides in \ref kNamespace, and false if it resides + * in no namespace at all + */ + const bool in_namespace; + + ~StopNode() override = default; +}; // class StopNode + +/** + * Model a text node. + */ +class TextNode : public INode { + public: + explicit TextNode( + std::string a_text + ) : + text(std::move(a_text)) { + // Intentionally empty. + } + + NodeKind kind() const override { return NodeKind::Text; } + + /** + * UTF-8 encoded XML text somewhere within an XML element + */ + const std::string text; + + ~TextNode() override = default; +}; // class TextNode + +/** + * Model an end-of-input. + */ +class EofNode : public INode { + public: + NodeKind kind() const override { return NodeKind::Eof; } + + ~EofNode() override = default; +}; // class EofNode + +/** + * Model a low-level XML parsing error. + */ +class ErrorNode : public INode { + public: + ErrorNode( + size_t a_line, + size_t a_column, + std::string a_cause + ) : + line(a_line), + column(a_column), + cause(std::move(a_cause)) { + // Intentionally empty. + } + + NodeKind kind() const override { return NodeKind::Error; } + + const size_t line; + const size_t column; + + // Cause of the error as UTF-8 encoded string + const std::string cause; + + ~ErrorNode() override = default; +}; // class ErrorNode + +// endregion Nodes + +/** + * Render the node in a human-readable form, meant for error messages. + */ +std::string NodeToHumanReadableString(const INode& node); + +/** + * Render the node in a human-readable form, meant for error messages. + */ +std::wstring NodeToHumanReadableWstring(const INode& node); + +/** + * Check that the given node is a stop node residing in \ref kNamespace and + * that its name corresponds to the expected name. + */ +bool IsStopNodeWithName( + const INode& node, + const std::string& expected_name +); + +/** + * Check that the given node is a stop node residing in no namespace at all and + * that its name corresponds to the expected name. + */ +bool IsStopNodeWithNameInNoNamespace( + const INode& node, + const std::string& expected_name +); + +// region XSD lexical forms + +/** + * \brief Normalize \p text the way `whiteSpace="collapse"` prescribes. + * + * Every atomic XSD type except a string, and every type derived from one by + * restriction, fixes `whiteSpace` to `collapse`, and a schema author can not + * change it. A tab, a line feed and a carriage return each become a space, + * a run of spaces becomes one space, and the leading and trailing spaces go. + * Only the result of that is a lexical representation to be matched. + * + * Mind that this strips only the whitespace *around* the value: a space + * within it survives as a single space, so `2 3` becomes `2 3`, which is + * still no number. + * + * See: https://www.w3.org/TR/xmlschema-2/#rf-whiteSpace + */ +std::string CollapseWhitespace(const std::string& text); + +/** + * \brief Tell whether \p text is a numeral of the `xs:long` lexical space. + * + * [-+]?[0-9]+ + * + * The range is left to the parser, which refuses what does not fit + * a 64-bit integer. + * + * See: https://www.w3.org/TR/xmlschema-2/#long + */ +bool MatchesXsLongNumeral(const std::string& text); + +/** + * \brief Tell whether \p text is a numeral of the `xs:double` lexical space. + * + * The three named literals -- `INF`, `-INF` and `NaN` -- are matched by + * the caller, so only the numeral is considered here: + * + * (\+|-)? ( [0-9]+ (\.[0-9]*)? | \.[0-9]+ ) ([Ee](\+|-)?[0-9]+)? + * + * This is spelled out rather than left to `std::regex` so that no pattern has + * to be compiled, and rather than left to `std::stod` because that one reads + * far more than XSD admits. + * + * See: https://www.w3.org/TR/xmlschema-2/#double + */ +bool MatchesXsDoubleNumeral(const std::string& text); + +/** + * \brief Tell whether \p text is a lexical form of `xs:base64Binary`. + * + * The whitespace is expected to be gone already. What is left has to match + * `(B64 B64 B64 B64)* ((B64 B64 B64 B64) | (B64 B64 B16 '=') | (B64 B04 '=='))?` + * -- a length which is a multiple of four, the alphabet and nothing else, + * an equals sign only at the very end, and, easily missed, a constrained + * character *before* the padding, as the bits which the padding drops have to + * be zero. + * + * The decoders do not agree on any of this, so every target does the same + * check of its own and refuses the same texts. + * + * See: https://www.w3.org/TR/xmlschema-2/#base64Binary + */ +bool MatchesXsBase64Binary(const std::string& text); + +/** + * \brief Drop every whitespace character of \p text. + * + * This is what `xs:base64Binary` needs: it allows whitespace between + * the characters and not only around them, so collapsing is not enough -- + * the decoder accepts none of it. + * + * See: https://www.w3.org/TR/xmlschema-2/#base64Binary + */ +std::string RemoveWhitespace(const std::string& text); + +// endregion XSD lexical forms + +// region Reading + +// region class Reader + +// NOTE (mristin): +// The full definition is given in the implementation file, and only forward-declared +// here since it is passed around exclusively through ``std::unique_ptr``. +struct OurData; + +/** + * \brief Read XML in form of nodes, whereas text nodes are fragmented. + * + * We need a more abstract approach since the Expat library is too low-level + * to parse complex models. + * + * Expat does not read the whole content of a text node in memory, but + * we need to process the whole text during the XML de-serialization. Hence, + * we keep on reading until we read the complete text. This has repercussions + * on memory usage, as the the text will be held in three copies(one copy in + * the Expat buffer, second copy in our internal buffer in which we + * incrementally feed in the fragments, and the third copy is the final merged + * text). + */ +class Reader { + public: + /** + * \param is stream to read the XML from + * \param additional_attributes if set, unexpected XML attributes are ignored + * instead of being reported as errors + * \param buffer_size size of the chunk to be read from \p is and passed to + * the underlying XML parser + * + * Every element is expected to reside either in \ref kNamespace or in no + * namespace at all -- the latter is how the XML-RPC subset, over which + * a JSON-able value is de/serialized, is written. An element in any other + * namespace is reported as an error. Which of the two an element is expected + * to reside in depends on the grammar, so the node says which one it was, and + * the caller decides. + */ + Reader( + std::istream& is, + bool additional_attributes, + size_t buffer_size + ); + + /** + * Set up the reader for the XML parsing and read the first node. + */ + void Initialize(); + + /** + * Read the next node in the document. + */ + void Read(); + + /** + * @return the node which has been read last + */ + const INode& node() const; + + /** + * @return the node which has been read last moved out of this reader + */ + std::unique_ptr moved_node(); + + ~Reader(); + + private: + const bool additional_attributes_; + const size_t buffer_size_; + std::istream& is_; + + XML_Parser parser_; + std::unique_ptr our_data_; + + // Node buffer does not include the current node. + std::deque> node_buffer_; + + // Current node is never null. + std::unique_ptr current_; + + // Set if the current node is end-of-input + bool eof_; + + // Set if the current node is an error + bool error_; + + void SetCurrentAndEofAndError(std::unique_ptr node); + + // Re-usable buffer to keep a chunk of the data read from the input + std::vector chunk_; +}; // class Reader + +// endregion class Reader + +// region class ReaderMergingText + +/** + * \brief Read XML in forms of nodes, with text nodes read in whole. + * + * This is a reader on top of the \ref Reader which keeps the text fragments + * in the buffer. We need to process the text in whole during the XML + * de-serialization, so this buffering is necessary. However, this means that + * the text is kept in four copies (one partial copy in Expat buffer, + * another partial copy as fragmented text nodes in the underlying \ref Reader + * instance, yet another copy in the internal buffer of this instance, and + * finally the fourth copy as the merged complete text). + */ +class ReaderMergingText { + public: + ReaderMergingText( + std::istream& is, + bool additional_attributes, + size_t buffer_size + ); + + /** + * Set up the reader for the XML parsing and read the first node. + */ + void Initialize(); + + /** + * Read the next node in the document. + */ + void Read(); + + /** + * @return the node which has been read last + */ + const INode& node() const; + + /** + * @return set if the current node represents an error + */ + bool error() const; + + /** + * @return set if the current node represents an end-of-input + */ + bool eof() const; + + private: + bool initialized_; + Reader reader_; + + std::unique_ptr current_; + std::unique_ptr look_ahead_; + + // Assuming that the underlying reader points to a text node, + // read all the consecutive text nodes and set the look-ahead node + void ReadAndMergeAllTextAndSetLookahead(); + + bool error_; + bool eof_; + void SetCurrentAndEofAndError( + std::unique_ptr node + ); +}; // class ReaderMergingText + +// endregion class ReaderMergingText + +// endregion Reading + +// region Writing + +/** + * Represent an error caught by \ref SelfClosingWriter. + */ +struct SerializationError { + /** + * Human-readable description of the error + */ + std::wstring cause; + + /** + * Path to the value that caused the error + */ + iteration::Path path; + + explicit SerializationError( + std::wstring a_cause + ); +}; // struct SerializationError + +extern const std::wstring kTheOutputStreamIsInABadState; + +/** + * Check that the output stream is not in a bad state. If so, create an error. + */ +common::optional CheckOstreamState( + const std::ostream& os +); + +// region class SelfClosingWriter + +/** + * \brief Write XML elements, shortening an element with no content to + * a self-closing element. + */ +class SelfClosingWriter { + public: + /** + * \param os to write to + * \param root_attributes attributes of the outermost element, written + * verbatim and each preceded by a space; the declaration of \ref kNamespace, + * or nothing at all + */ + SelfClosingWriter( + std::ostream& os, + const char* root_attributes + ); + + /** + * Queue a start element for an eventual write. + */ + void StartElement( + std::string name + ); + + /** + * Write a stop element. + * + * If there is a pending start element with no content, shorten it to + * a self-closing XML element. + */ + void StopElement( + const std::string& name + ); + + /** + * \brief Serialize the given boolean to an xs:bool value. + * + * We explicitly write longer text, `true` and `false`, to make the values explicit, + * and not potentially confusing with numbers, in the XML. + */ + void SerializeBool( + bool value + ); + + /** + * \brief Serialize the given number to an xs:long value. + * + * We do not check that the number is within a range representable as 64-bit + * floats, as the value can be de-serialized correctly from XML. However, this + * means that XML and JSON serializations are not interoperable. If you need + * interoperability, you have to ensure that range yourself (e.g., through + * \ref validation). + */ + void SerializeInt64( + int64_t value + ); + + /** + * \brief Serialize the given number to an xs:double value. + */ + void SerializeDouble( + double value + ); + + /** + * \brief Write the text while escaping special characters for XML. + */ + void SerializeWstring( + const std::wstring& text + ); + + /** + * \brief Write the text while escaping special characters for XML. + */ + void SerializeString( + const std::string& text + ); + + /** + * \brief Encode bytes to Base64 and write them. + */ + void SerializeByteArray( + const std::vector& byte_array + ); + + /** + * Finish and flush any pending start nodes. + */ + void Finish(); + + /** + * Get an error, if any, caught during the serialization. + */ + const common::optional& error() const; + + /** + * Transfer the ownership of the error. + */ + common::optional&& move_error(); + + private: + std::ostream& os_; + common::optional error_; + common::optional pending_start_wo_text_; + + /** + * \brief Attributes of the outermost element, until it has been queued. + * + * The very first \ref StartElement takes them and leaves nothing behind, so + * that the namespace is declared on the root element and on no other. + */ + const char* root_attributes_; + + /** + * Attributes of the pending start element; the declaration of the namespace + * for the outermost element, its undeclaration for the outermost element + * which resides in no namespace, and nothing otherwise + */ + const char* pending_attributes_; + + /** + * \brief Escape the given text to XML. + * + * Return nothing if no escaping was needed. + */ + static common::optional EscapeForXml( + const std::wstring& text + ); + + /** + * \brief Escape the given text to XML. + * + * Return nothing if no escaping was needed. + */ + static common::optional EscapeForXml( + const std::string& text + ); + + void WritePendingStartElementIfAvailable(); + + /** + * Write the text without any XML escaping or flushing of pending start elements. + */ + void WriteStringWithoutEscapingNorFlushing( + const std::string& text + ); +}; // class SelfClosingWriter + +// endregion class SelfClosingWriter + +// endregion Writing + +} // namespace xml_common + +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#endif // DUMMY_XML_COMMON_GUARD_ diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_path.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_path.cpp new file mode 100644 index 000000000..e6da8cd36 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xml_path.cpp @@ -0,0 +1,239 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/xml_path.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +namespace dummy { +namespace xml_path { + +namespace { + +/** + * \brief Escape \p text so that it can be directly inserted into an XPath. + * + * \param text to be escaped + * \return the escaped text + */ +std::wstring EscapeForXPath( + const std::wstring& text +) { + size_t out_len = 0; + for (const wchar_t character : text) { + switch (character) { + // NOTE (mristin): + // We use sizeof on *strings* instead of *wide strings* to get + // the number of *characters*. Otherwise, if we used wide strings, + // we would obtain the wrong number of characters with `sizeof` + // as we would count bytes instead of characters, which differ + // in wide strings due to encoding. + + case L'&': { + out_len += sizeof("&"); + break; + } + case L'/': { + out_len += sizeof("/"); + break; + } + case L'<': { + out_len += sizeof("<"); + break; + } + case L'>': { + out_len += sizeof(">"); + break; + } + case L'"': { + out_len += sizeof("""); + break; + } + case L'\'': { + out_len += sizeof("'"); + break; + } + default: + ++out_len; + break; + } + } + + // NOTE (mristin): + // We assume here that XML encoding is always *longer* than + // the original text. + if (out_len == text.size()) { + return text; + } + + std::wstring out; + out.reserve(out_len); + + for (const wchar_t character : text) { + switch (character) { + case L'&': + out.append(L"&"); + break; + case L'/': + out.append(L"/"); + break; + case L'<': + out.append(L"<"); + break; + case L'>': + out.append(L">"); + break; + case L'"': + out.append(L"""); + break; + case L'\'': + out.append(L"'"); + break; + default: + out.push_back(character); + break; + } + } + + return out; +} + +} // namespace + +// region struct ElementSegment + +ElementSegment::ElementSegment( + std::wstring a_name +) : + name(std::move(a_name)) { + // Intentionally empty. +} + +std::wstring ElementSegment::ToWstring() const { + return EscapeForXPath(name); +} + +std::unique_ptr ElementSegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct ElementSegment + +// region struct MemberSegment + +MemberSegment::MemberSegment( + std::wstring a_name +) : + name(std::move(a_name)) { + // Intentionally empty. +} + +std::wstring MemberSegment::ToWstring() const { + return common::Concat( + L"member[name=\"", + EscapeForXPath(name), + L"\"]" + ); +} + +std::unique_ptr MemberSegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct MemberSegment + +// region struct IndexSegment + +IndexSegment::IndexSegment( + size_t an_index +) : + index(an_index) { + // Intentionally empty. +} + +std::wstring IndexSegment::ToWstring() const { + return common::Concat( + L"*[", + std::to_wstring(index + 1), + L"]" + ); +} + +std::unique_ptr IndexSegment::Clone() const { + return common::make_unique(*this); +} + +// endregion struct IndexSegment + +// region struct Path + +Path::Path() { + // Intentionally empty. +} + +Path::Path(const Path& other) { + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } +} + +Path::Path(Path&& other) { + segments = std::move(other.segments); +} + +Path& Path::operator=(const Path& other) { + segments.clear(); + for (const std::unique_ptr& segment : other.segments) { + segments.emplace_back(segment->Clone()); + } + return *this; +} + +Path& Path::operator=(Path&& other) { + if (this != &other) { + segments = std::move(other.segments); + } + return *this; +} + +std::wstring Path::ToWstring() const { + if (segments.empty()) { + return L""; + } + + std::vector parts; + parts.reserve(segments.size() * 2 - 1); + + auto it = segments.begin(); + + parts.emplace_back((*it)->ToWstring()); + ++it; + + for (; it != segments.end(); ++it) { + parts.emplace_back(L"/"); + parts.emplace_back((*it)->ToWstring()); + } + + size_t out_len = 0; + for (const std::wstring& part : parts) { + out_len += part.size(); + } + + std::wstring out; + out.reserve(out_len); + for (const std::wstring& part : parts) { + out.append(part); + } + + return out; +} + +} // namespace xml_path +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xmlization.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xmlization.cpp new file mode 100644 index 000000000..92ee9b02e --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/src/xmlization.cpp @@ -0,0 +1,3185 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/stringification.hpp" +#include "dummy/wstringification.hpp" +#include "dummy/xmlization.hpp" + +#include "xml_common.hpp" + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +namespace dummy { +namespace xmlization { + +const std::string kNamespace( // NOLINT(cert-err58-cpp) + xml_common::kNamespace +); + +// region De-serialization + +// region DeserializationError + +DeserializationError::DeserializationError( + std::wstring a_cause +) : + cause(a_cause) { + // Intentionally empty. +} + +DeserializationError::DeserializationError( + std::wstring a_cause, + xml_path::Path a_path +) : + cause(a_cause), + path(a_path) { + // Intentionally empty. +} + +// endregion DeserializationError + +// region Forward declarations of de-serialization functions + +// NOTE (mristin): +// We make forward declarations of de-serialization functions so that they can be +// called in any order. + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ItemFromElement( + xml_common::ReaderMergingText& reader +); + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> SomeItemFromElement( + xml_common::ReaderMergingText& reader +); + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> SomeItemFromSequence( + xml_common::ReaderMergingText& reader +); + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> SpecialItemFromElement( + xml_common::ReaderMergingText& reader +); + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> SpecialItemFromSequence( + xml_common::ReaderMergingText& reader +); + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ContainerFromElement( + xml_common::ReaderMergingText& reader +); + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> ContainerFromSequence( + xml_common::ReaderMergingText& reader +); + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> WrapperFromElement( + xml_common::ReaderMergingText& reader +); + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* = nullptr +> +std::pair< + common::optional >, + common::optional +> WrapperFromSequence( + xml_common::ReaderMergingText& reader +); + +// endregion Forward declarations of de-serialization functions + +/** + * Map XML class names to model types. + */ +const std::unordered_map< + std::string, + types::ModelType +> kElementNameToModelType = { + { + "someItem", + types::ModelType::kSomeItem + }, + { + "specialItem", + types::ModelType::kSpecialItem + }, + { + "container", + types::ModelType::kContainer + }, + { + "wrapper", + types::ModelType::kWrapper + } +}; + +common::optional ModelTypeFromElementName( + const std::string& element_name +) { + auto it = kElementNameToModelType.find(element_name); + if (it == kElementNameToModelType.end()) { + return common::nullopt; + } + + return it->second; +} + +template < + typename T +> +std::pair< + common::optional, + common::optional +> InstanceAndNoDeserializationError( + T&& instance +) { + return std::make_pair< + common::optional, + common::optional + >( + std::move(instance), + common::nullopt + ); +} + +template < + typename T +> std::pair< + common::optional, + common::optional +> NoInstanceAndDeserializationErrorWithCause( + std::wstring cause +) { + return std::make_pair< + common::optional, + common::optional + >( + common::nullopt, + common::make_optional( + std::move(cause) + ) + ); +} + +DeserializationError DuplicatePropertyError( + const std::string& name +) { + return DeserializationError( + common::Concat( + L"Property ", + common::Utf8ToWstring(name), + L" occurred more than once" + ) + ); +} + +DeserializationError DeserializationErrorFromReader( + xml_common::ReaderMergingText& reader +) { + if (reader.node().kind() != xml_common::NodeKind::Error) { + throw std::logic_error( + common::Concat( + "Expected an error node at the reader cursor, but got ", + xml_common::NodeToHumanReadableString(reader.node()) + ) + ); + } + + const auto error_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::ErrorNode& + >(reader.node()) + ); + + return DeserializationError( + common::Utf8ToWstring( + error_node.cause + ) + ); +} + +template < + typename T +> +std::pair< + common::optional, + common::optional +> NoInstanceAndDeserializationErrorFromReader( + xml_common::ReaderMergingText& reader +) { + if (reader.node().kind() != xml_common::NodeKind::Error) { + throw std::logic_error( + common::Concat( + "Expected an error node at the reader cursor, but got ", + xml_common::NodeToHumanReadableString(reader.node()) + ) + ); + } + + DeserializationError error = DeserializationErrorFromReader( + reader + ); + + return std::make_pair( + common::nullopt, + common::make_optional( + std::move(error) + ) + ); +} + +template < + typename T +> +std::pair< + common::optional, + common::optional +> NoInstanceAndDeserializationError( + DeserializationError error +) { + return std::make_pair( + common::nullopt, + common::make_optional( + std::move(error) + ) + ); +} + +void PrependElementSegmentToDeserializationError( + const std::string& name, + DeserializationError& deserialization_error +) { + deserialization_error.path.segments.emplace_front( + common::make_unique( + common::Utf8ToWstring(name) + ) + ); +} + +common::optional CheckReaderAtEof( + const xml_common::ReaderMergingText& reader +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in CheckReaderAtEof. " + "CheckReaderAtEof expects no reader error at entry." + ); + } + #endif + + if (reader.node().kind() != xml_common::NodeKind::Eof) { + return common::make_optional( + common::Concat( + L"Expected end-of-input, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + return common::nullopt; +} + +/** + * \brief Skip the beginning-of-file (BoF) and read a node. + * + * Do nothing if the cursor points to a non-BoF node. + * + * Return an error if the reader produced an error. + */ +common::optional SkipBof( + xml_common::ReaderMergingText& reader +) { + if (reader.node().kind() != xml_common::NodeKind::Bof) { + return common::nullopt; + } + + reader.Read(); + if (reader.node().kind() == xml_common::NodeKind::Error) { + return DeserializationErrorFromReader(reader); + } + + return common::nullopt; +} + +/** + * Return `true` if all characters are whitespace in the UTF-8-encoded text. + */ +bool IsWhitespace(const std::string& utf8_text) { + for (const char character : utf8_text) { + switch (character) { + // NOTE (mristin): + // The characters are ordered by their ASCII codes so that + // we allow compilers to optimize. + + // NOTE (mristin): + // Text nodes contain text in UTF-8 which is compatible with ASCII. + // In particular, all characters above ASCII (>127) are encoded with + // all the leading bits set. Hence, it is safe to check for whitespace + // characters in an UTF-8-encoded string using one-byte characters. + // + // See: https://stackoverflow.com/questions/15965811/why-utf8-is-compatible-with-ascii + + case '\t': + case '\n': + case '\r': + case ' ': + // Pass + break; + default: + return false; + } + } + + return true; +} + +/** + * \brief Skip all whitespace text nodes. + * + * Do nothing if the cursor points to a non-text node. + * + * The whitespace includes space, tab, carriage return and newline. + * + * Return an error if the reader produced an error. + */ +common::optional SkipWhitespace( + xml_common::ReaderMergingText& reader +) { + while (reader.node().kind() == xml_common::NodeKind::Text) { + const xml_common::TextNode& text_node( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >( + reader.node() + ) + ); + + if (!IsWhitespace(text_node.text)) { + break; + } + + reader.Read(); + } + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return DeserializationErrorFromReader(reader); + } + + return common::nullopt; +} + +/** + * \brief Create the exception to be thrown on an unexpected property literal. + * + * Every ``switch`` over the properties of a class covers all the literals of + * its enumeration, so we can only get here if the value has been corrupted. + * We report that as a logic error, and not as a de-serialization error, since + * it does not originate in the input. + * + * \param enum_name name of the property enumeration, for the message + * \param property the unexpected literal + * \return the exception to be thrown + */ +template +std::logic_error UnexpectedPropertyLiteralError( + const char* enum_name, + EnumT property +) { + return std::logic_error( + common::Concat( + "Unexpected properties literal of ", + enum_name, + ": ", + std::to_string( + static_cast(property) + ) + ) + ); +} + +/** + * \brief Read the properties of an instance as a sequence of XML elements. + * + * The cursor is expected to point at the content of the element which opens + * the instance. On success, the cursor points at the stop element closing it, + * which the caller is expected to consume. + * + * This function factors out everything which the property loop of a class does + * not say about the class it belongs to: skipping the whitespace, recognizing + * the end of the sequence, reading and consuming the start element, looking + * the property up, refusing a duplicate, marking the property on the error path + * and consuming the stop element. The class itself supplies only \p on_property, + * which dispatches on the property and assigns the local variables that its + * constructor is finally called with. + * + * NOTE (mristin): + * We take \p map_of_properties in, instead of letting \p on_property work on + * the XML name of the property, because we want the class to dispatch with + * a hard-wired ``switch`` whose branches assign the local variables of + * the caller. + * + * A ``switch`` needs an integral constant, and C++ can not switch on a string, + * so the name has to be translated into a literal of the property enumeration + * first. We do that here rather than in the class so that the translation, and + * the error reported when the name matches no property at all, are written + * once instead of once per class. + * + * The alternative -- mapping the name directly to the code which reads + * the property -- would cost a type-erased, capturing callable per property, + * built anew on every single read, since the code has to assign the caller's + * variables. The ``switch`` costs a jump table. + * + * \tparam kPropertyCount number of the properties of the class + * \param reader to read from + * \param map_of_properties maps the XML name of a property to its literal + * \param interface_name name of the interface, for the messages + * \param on_property reads the content of the recognized property + * \return the error, if the reading failed + */ +template +common::optional ReadProperties( + xml_common::ReaderMergingText& reader, + const std::unordered_map& map_of_properties, + const wchar_t* interface_name, + const OnPropertyT& on_property +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in ReadProperties. " + "ReadProperties expects no reader error at entry." + ); + } + #endif + + common::optional error; + + error = SkipBof(reader); + if (error.has_value()) { + return error; + } + + // NOTE (mristin): + // Whether a property has already occurred is a bookkeeping of this loop, and + // not of the property, so we track it here, indexed by the property literal, + // instead of asking every single target variable at every single case of + // the ``switch``. + // + // The bit set is sized to the class, so a class may have arbitrarily many + // properties, and it lives on the stack, so nothing is allocated for it on + // a read. + std::bitset seen; + + while (true) { + error = SkipWhitespace(reader); + if (error.has_value()) { + return error; + } + + if (reader.node().kind() == xml_common::NodeKind::Stop) { + // NOTE (mristin): + // We reached a closing element of an instance, so we know that + // the sequence ended. + return common::nullopt; + } else if (reader.node().kind() != xml_common::NodeKind::Start) { + return DeserializationError( + common::Concat( + L"Expected a start element opening a property of ", + interface_name, + L", but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string name( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::StartNode& + >(reader.node()).name + ); + + // NOTE (mristin): + // We consume the start element. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + error = DeserializationErrorFromReader(reader); + + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + + auto it = map_of_properties.find(name); + if (it == map_of_properties.end()) { + return DeserializationError( + common::Concat( + L"Expected a start element opening a property of ", + interface_name, + L", but got a start element " + L"which does not correspond to any of its properties: <", + common::Utf8ToWstring(name), + L">" + ) + ); + } + + const EnumT property(it->second); + + // NOTE (mristin): + // The literals of a property enumeration run from zero without a gap, and + // the map maps only to them, so the index is always in the range of + // the bit set. We check that only in debug builds, and index unchecked + // otherwise. + const std::size_t index( + static_cast(property) + ); + + #ifdef DEBUG + if (index >= kPropertyCount) { + throw std::logic_error( + common::Concat( + "Unexpected property index in ReadProperties: ", + std::to_string(index), + ", but there are only ", + std::to_string(kPropertyCount), + " properties" + ) + ); + } + #endif + + // NOTE (mristin): + // An engaged bit can only have been set by an earlier turn of this loop, so + // it tells us that the property comes a second time. The check precedes + // the read, so the duplicate is refused without its content ever being + // looked at. + if (seen[index]) { + error = DuplicatePropertyError(name); + + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + + seen[index] = true; + + error = on_property(property); + if (error.has_value()) { + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + + error = SkipWhitespace(reader); + if (error.has_value()) { + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + + if (!xml_common::IsStopNodeWithName(reader.node(), name)) { + error = DeserializationError( + common::Concat( + L"Expected a stop element closing the property of ", + interface_name, + L", but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + + // NOTE (mristin): + // We consume the stop element. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + error = DeserializationErrorFromReader(reader); + + PrependElementSegmentToDeserializationError( + name, + *error + ); + + return error; + } + } +} + +template +std::pair< + common::optional, + common::optional +> DeserializeFromElement( + xml_common::ReaderMergingText& reader, + const wchar_t* value_name, + const DispatchT& dispatch +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeFromElement. " + "DeserializeFromElement expects no reader error at entry." + ); + } + #endif + + common::optional error; + + error = SkipBof(reader); + if (error.has_value()) { + return NoInstanceAndDeserializationError(std::move(*error)); + } + + error = SkipWhitespace(reader); + if (error.has_value()) { + return NoInstanceAndDeserializationError(std::move(*error)); + } + + if (reader.node().kind() != xml_common::NodeKind::Start) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected a start element opening an instance of ", + value_name, + L", but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string name( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::StartNode& + >(reader.node()).name + ); + + common::optional model_type( + ModelTypeFromElementName(name) + ); + if (!model_type.has_value()) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Unexpected start element as its name does not correspond " + L"to any model type: ", + common::Utf8ToWstring(name) + ) + ); + } + + // NOTE (mristin): + // We consume the start element. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader(reader); + } + + common::optional instance; + std::tie( + instance, + error + ) = dispatch(reader, *model_type, name); + + if (error.has_value()) { + return NoInstanceAndDeserializationError(std::move(*error)); + } + + error = SkipWhitespace(reader); + if (error.has_value()) { + return NoInstanceAndDeserializationError(std::move(*error)); + } + + if (!xml_common::IsStopNodeWithName(reader.node(), name)) { + error = DeserializationError( + common::Concat( + L"Expected a stop element closing an instance of ", + value_name, + L", but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + + return NoInstanceAndDeserializationError(std::move(*error)); + } + + // NOTE (mristin): + // We consume the stop element. + reader.Read(); + if (reader.node().kind() == xml_common::NodeKind::Error) { + error = DeserializationErrorFromReader(reader); + return NoInstanceAndDeserializationError(std::move(*error)); + } + + return InstanceAndNoDeserializationError( + std::move(*instance) + ); +} + +template +std::pair< + common::optional, + common::optional +> DeserializeSoleFromElement( + xml_common::ReaderMergingText& reader, + const wchar_t* value_name, + types::ModelType expected_model_type, + std::pair< + common::optional, + common::optional + > (*from_sequence)(xml_common::ReaderMergingText&) +) { + return DeserializeFromElement( + reader, + value_name, + [value_name, expected_model_type, from_sequence]( + xml_common::ReaderMergingText& a_reader, + types::ModelType a_model_type, + const std::string& a_name + ) -> std::pair< + common::optional, + common::optional + > { + if (a_model_type != expected_model_type) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Impossible to de-serialize an instance of ", + value_name, + L" from <", + common::Utf8ToWstring(a_name), + L">" + ) + ); + } + + return from_sequence(a_reader); + } + ); +} + +/** + * \brief De-serialize a value from the element nested in another element, such + * as the instance in a property, or from the root element. + * + * We prepend the name of the nested element to the path of an error, so that + * the path reads, *e.g.*, value/property/idShort. + * + * This looks redundant next to reading an item of a list, and it is not. + * An item is not nested in an element of its own -- the item element *is* + * the indexed child -- so its readers prepend only the index, and the path + * reads, *e.g.*, annotations/*[1]/idShort. + * + * \param reader to read from + * \param deserialize_element de-serializes the nested element + * \return the value, or an error, if any + */ +template +std::pair< + common::optional, + common::optional +> DeserializeNestedElement( + xml_common::ReaderMergingText& reader, + const DeserializeT& deserialize_element +) { + common::optional error = SkipWhitespace(reader); + if (error.has_value()) { + return NoInstanceAndDeserializationError(std::move(*error)); + } + + if (reader.node().kind() != xml_common::NodeKind::Start) { + return deserialize_element(reader); + } + + const std::string name( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::StartNode& + >(reader.node()).name + ); + + std::pair< + common::optional, + common::optional + > result = deserialize_element(reader); + + if (result.second.has_value()) { + PrependElementSegmentToDeserializationError( + name, + *(result.second) + ); + } + + return result; +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ClassFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeFromElement< + std::shared_ptr + >( + reader, + L"IClass", + []( + xml_common::ReaderMergingText& a_reader, + types::ModelType a_model_type, + const std::string& a_name + ) -> std::pair< + common::optional >, + common::optional + > { + switch (a_model_type) { + case types::ModelType::kSomeItem: + return SomeItemFromSequence< + types::IClass + >(a_reader); + case types::ModelType::kSpecialItem: + return SpecialItemFromSequence< + types::IClass + >(a_reader); + case types::ModelType::kContainer: + return ContainerFromSequence< + types::IClass + >(a_reader); + case types::ModelType::kWrapper: + return WrapperFromSequence< + types::IClass + >(a_reader); + default: + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"Impossible to de-serialize an instance " + L"of IClass from <", + common::Utf8ToWstring(a_name), + L">" + ) + ); + } + } + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ItemFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeFromElement< + std::shared_ptr + >( + reader, + L"IItem", + []( + xml_common::ReaderMergingText& a_reader, + types::ModelType a_model_type, + const std::string& a_name + ) -> std::pair< + common::optional >, + common::optional + > { + switch (a_model_type) { + case types::ModelType::kSomeItem: + return SomeItemFromSequence< + types::IItem + >(a_reader); + case types::ModelType::kSpecialItem: + return SpecialItemFromSequence< + types::IItem + >(a_reader); + default: + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"Impossible to de-serialize an instance " + L"of IItem from <", + common::Utf8ToWstring(a_name), + L">" + ) + ); + } + } + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> SomeItemFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeFromElement< + std::shared_ptr + >( + reader, + L"ISomeItem", + []( + xml_common::ReaderMergingText& a_reader, + types::ModelType a_model_type, + const std::string& a_name + ) -> std::pair< + common::optional >, + common::optional + > { + switch (a_model_type) { + case types::ModelType::kSomeItem: + return SomeItemFromSequence< + types::ISomeItem + >(a_reader); + case types::ModelType::kSpecialItem: + return SpecialItemFromSequence< + types::ISomeItem + >(a_reader); + default: + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + common::Concat( + L"Impossible to de-serialize an instance " + L"of ISomeItem from <", + common::Utf8ToWstring(a_name), + L">" + ) + ); + } + } + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> SpecialItemFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeSoleFromElement< + std::shared_ptr + >( + reader, + L"ISpecialItem", + types::ModelType::kSpecialItem, + SpecialItemFromSequence + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> ContainerFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeSoleFromElement< + std::shared_ptr + >( + reader, + L"IContainer", + types::ModelType::kContainer, + ContainerFromSequence + ); +} + +std::pair< + common::optional< + std::shared_ptr + >, + common::optional +> WrapperFromElement( + xml_common::ReaderMergingText& reader +) { + return DeserializeSoleFromElement< + std::shared_ptr + >( + reader, + L"IWrapper", + types::ModelType::kWrapper, + WrapperFromSequence + ); +} + +// region De-serialize primitives + +const std::unordered_map< + std::string, + bool +> kTextToBool = { + {"true", true}, + {"false", false}, + {"1", true}, + {"0", false} +}; + +std::pair< + common::optional, + common::optional +> DeserializeBool( + xml_common::ReaderMergingText& reader +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeBool. " + "DeserializeBool expects no error node." + ); + } + #endif + + if (reader.node().kind() != xml_common::NodeKind::Text) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:boolean from XML text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string text( + xml_common::CollapseWhitespace( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >(reader.node()).text + ) + ); + + auto it = kTextToBool.find(text); + if (it == kTextToBool.end()) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:boolean from text, " + L"but got an invalid value: ", + common::Utf8ToWstring(text) + ) + ); + } + + // NOTE (mristin): + // We consume the text node. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader(reader); + } + + return std::make_pair( + it->second, + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> DeserializeInt64( + xml_common::ReaderMergingText& reader +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeInt64. " + "DeserializeInt64 expects no error node." + ); + } + #endif + + if (reader.node().kind() != xml_common::NodeKind::Text) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:long from XML text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string text( + xml_common::CollapseWhitespace( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >(reader.node()).text + ) + ); + + // NOTE (mristin): + // The lexical form has to be checked before the text is parsed. The std::sto* + // family reads far more than XSD admits: it stops at the first character it + // can not use and answers with what it has read so far, so "0x10" comes out + // as 0, "1abc" as 1, and "5.0" as 5. + if (!xml_common::MatchesXsLongNumeral(text)) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:long from text, " + L"but got an invalid value: ", + common::Utf8ToWstring(text) + ) + ); + } + + common::optional deserialized; + + static_assert( + sizeof(int) == 8 + || sizeof(long) == 8 + || sizeof(long long) == 8, + "Neither int nor long nor long long are 8 bytes long, " + "so we do not know how to parse an xs:long." + ); + + try { + // NOTE (mristin): + // We remove the warning C4101 in MSVC with constants. + // See: https://stackoverflow.com/questions/25573996/c4127-conditional-expression-is-constant + const bool sizeof_int_is_8 = sizeof(int) == 8; + const bool sizeof_long_is_8 = sizeof(long) == 8; + const bool sizeof_long_long_is_8 = sizeof(long long) == 8; + + if (sizeof_int_is_8) { + deserialized = std::stoi(text); + } else if (sizeof_long_is_8) { + deserialized = std::stol(text); + } else if (sizeof_long_long_is_8) { + deserialized = std::stoll(text); + } + } catch (std::invalid_argument&) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:long from text, " + L"but got an invalid value: ", + common::Utf8ToWstring(text) + ) + ); + } catch (std::out_of_range&) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:long from text, " + L"but got a value out of the xs:long range: ", + common::Utf8ToWstring(text) + ) + ); + } + + if (!deserialized.has_value()) { + throw std::logic_error( + "Neither int nor long nor long long are 8 bytes long, " + "but this should have been caught earlier in the static assert" + ); + } + + // NOTE (mristin): + // We consume the text node. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader(reader); + } + + return std::make_pair( + deserialized, + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> DeserializeDouble( + xml_common::ReaderMergingText& reader +) { + static_assert( + sizeof(double) == 8, + "DeserializeDouble expects double to be 8 bytes, " + "but the size of the double is not 8 bytes" + ); + + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeDouble. " + "DeserializeDouble expects no error node." + ); + } + #endif + + if (reader.node().kind() != xml_common::NodeKind::Text) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:double from XML text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string text( + xml_common::CollapseWhitespace( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >(reader.node()).text + ) + ); + + // NOTE (mristin): + // XSD names the three special values, and it is case-sensitive about it. + // + // See: https://www.w3.org/TR/xmlschema11-2/#double + double deserialized; + + // NOTE (mristin): + // "+INF" is read although it is written as "INF": XSD 1.1 admits it, its + // production being (\+|-)?INF, and being liberal in what we accept costs + // nothing here. + if (text == "INF" || text == "+INF") { + deserialized = std::numeric_limits::infinity(); + } else if (text == "-INF") { + deserialized = -std::numeric_limits::infinity(); + } else if (text == "NaN") { + deserialized = std::numeric_limits::quiet_NaN(); + } else { + // NOTE (mristin): + // The lexical form has to be checked before the text is parsed. + // std::stod reads far more than XSD admits: a hexadecimal significand, + // so "0x10" comes out as 16; a trailing remainder, so "1.0abc" comes + // out as 1; and the spellings "inf", "infinity", "nan" and "NAN". + if (!xml_common::MatchesXsDoubleNumeral(text)) { + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:double from text, " + L"but got an invalid value: ", + common::Utf8ToWstring(text) + ) + ); + } + + // NOTE (mristin): + // std::stod refuses a literal whose value does not fit a double, by + // throwing std::out_of_range, and it does so at both ends. XSD asks for + // neither refusal: a literal too large rounds to an infinity, which is + // in the value space of xs:double, and one too small rounds to zero. + // std::strtod gives exactly that, reporting the range through errno, + // which we deliberately leave alone. + // + // The numeral has already been matched, so strtod consumes all of it. + deserialized = std::strtod(text.c_str(), nullptr); + } + + // NOTE (mristin): + // We consume the text node. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader(reader); + } + + return std::make_pair( + deserialized, + common::nullopt + ); +} + +std::pair< + common::optional, + common::optional +> DeserializeWstring( + xml_common::ReaderMergingText& reader +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeWstring. " + "DeserializeWstring expects no error node." + ); + } + #endif + + switch (reader.node().kind()) { + case xml_common::NodeKind::Stop: + // Encountering a stop node means that the string is empty. + return std::make_pair(std::wstring(), common::nullopt); + case xml_common::NodeKind::Text: + // We pass and continue decoding the text. + break; + default: + return NoInstanceAndDeserializationErrorWithCause( + common::Concat( + L"Expected to parse an xs:string from XML text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string& text( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >(reader.node()).text + ); + + std::wstring deserialized = common::Utf8ToWstring(text); + + // NOTE (mristin): + // We consume the text node. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader(reader); + } + + return std::make_pair(std::move(deserialized), common::nullopt); +} + +std::pair< + common::optional >, + common::optional +> DeserializeByteArray( + xml_common::ReaderMergingText& reader +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeByteArray. " + "DeserializeByteArray expects no error node." + ); + } + #endif + + switch (reader.node().kind()) { + case xml_common::NodeKind::Stop: + // Encountering a stop node means empty byte array. + return std::make_pair( + std::vector(), + common::nullopt + ); + case xml_common::NodeKind::Text: + // We pass and continue decoding the byte array. + break; + default: + return NoInstanceAndDeserializationErrorWithCause< + std::vector + >( + common::Concat( + L"Expected to parse an xs:base64Binary from XML text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + } + + const std::string text( + xml_common::RemoveWhitespace( + static_cast< // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast) + const xml_common::TextNode& + >(reader.node()).text + ) + ); + + if (!xml_common::MatchesXsBase64Binary(text)) { + return NoInstanceAndDeserializationErrorWithCause< + std::vector + >( + common::Concat( + L"Expected a text as base64-encoded bytes, but got: ", + common::Utf8ToWstring(text) + ) + ); + } + + common::expected< + std::vector, + std::string + > deserialized = stringification::Base64Decode(text); + + if (!deserialized.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::vector + >( + common::Concat( + L"Expected to parse an xs:base64Binary from text, " + L"but the value was invalid: ", + common::Utf8ToWstring(deserialized.error()) + ) + ); + } + + // NOTE (mristin): + // We consume the text node. + reader.Read(); + + if (reader.node().kind() == xml_common::NodeKind::Error) { + return NoInstanceAndDeserializationErrorFromReader< + std::vector + >(reader); + } + + return std::make_pair( + std::move(*deserialized), + common::nullopt + ); +} + +// endregion De-serialize primitives + +template +std::pair< + common::optional >, + common::optional +> DeserializeList( + xml_common::ReaderMergingText& reader, + const DeserializeT& deserialize_item +) { + #ifdef DEBUG + if (reader.node().kind() == xml_common::NodeKind::Error) { + throw std::logic_error( + "Unexpected unhandled XML error in DeserializeWstring. " + "DeserializeWstring expects no error node." + ); + } + #endif + + common::optional error; + + error = SkipWhitespace(reader); + if (error.has_value()) { + return std::make_pair( + common::nullopt, + std::move(error) + ); + } + + // If we encounter the stop element then we reached the end of the list. If this is + // the first node we encounter then the list is empty, *i.e.*, contains no items. + if (reader.node().kind() == xml_common::NodeKind::Stop) { + return std::make_pair( + std::vector(), + common::nullopt + ); + } else { + // NOTE (mristin): + // We use std::deque here as it is a buffered list, while a std::list + // would incur a memory allocation on each push. We do not want to use + // std::vector as the number of elements in a list can be arbitrarily large + // leading potentially to out-of-memory errors since std::vector's double + // their size for amortized time complexity of O(1) for insertions. + + std::deque items; + + size_t i = 0; + + while (true) { + // NOTE (mristin): + // Anything but an item element is an error of the list, not of an item, + // so we do not prepend any index to its path. + if (reader.node().kind() != xml_common::NodeKind::Start) { + error = DeserializationError( + common::Concat( + L"Expected only item elements and whitespace text, but got ", + xml_common::NodeToHumanReadableWstring(reader.node()) + ) + ); + break; + } + + common::optional item; + + std::tie( + item, + error + ) = deserialize_item(reader); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique(i) + ); + break; + } + + error = SkipWhitespace(reader); + if (error.has_value()) { + break; + } + + items.emplace_back(*item); + + if (reader.node().kind() == xml_common::NodeKind::Stop) { + break; + } + + ++i; + } + + if (!error.has_value()) { + auto result = std::vector(); + result.reserve(items.size()); + + for (auto& item : items) { + result.emplace_back( + std::move(item) + ); + } + + return std::make_pair( + std::move(result), + common::nullopt + ); + } else { + return std::make_pair( + common::nullopt, + std::move(error) + ); + } + } +} + +/** + * \brief Assign the value read to \p target, or return the error of the read. + * + * We deliberately take the *result* of a read instead of the reader and + * the function which reads. The item readers of a tuple vary both in number + * and in type, so no signature taking the reader could serve every read; + * taking the result lets this single function serve all of them. + * + * \param target variable to be assigned the value read + * \param read result of the read + * \return the error, if the read failed + */ +template +common::optional ReadInto( + common::optional& target, + std::pair< + common::optional, + common::optional + >&& read +) { + if (read.second.has_value()) { + return std::move(read.second); + } + + target = std::move(read.first); + + return common::nullopt; +} + +namespace properties { + +enum class OfSomeItem : std::uint32_t { + kName = 0, + kNickname = 1 +}; // enum class OfSomeItem + +enum class OfSpecialItem : std::uint32_t { + kName = 0, + kNickname = 1, + kLevel = 2 +}; // enum class OfSpecialItem + +enum class OfContainer : std::uint32_t { + kItems = 0, + kSomeItems = 1, + kSpecialItems = 2 +}; // enum class OfContainer + +enum class OfWrapper : std::uint32_t { + kContainer = 0 +}; // enum class OfWrapper + +const std::size_t kPropertyCountOfSomeItem = 2; + +const std::unordered_map< + std::string, + OfSomeItem +> kMapOfSomeItem = { + { + "name", + OfSomeItem::kName + }, + { + "nickname", + OfSomeItem::kNickname + } +}; + +const std::size_t kPropertyCountOfSpecialItem = 3; + +const std::unordered_map< + std::string, + OfSpecialItem +> kMapOfSpecialItem = { + { + "name", + OfSpecialItem::kName + }, + { + "nickname", + OfSpecialItem::kNickname + }, + { + "level", + OfSpecialItem::kLevel + } +}; + +const std::size_t kPropertyCountOfContainer = 3; + +const std::unordered_map< + std::string, + OfContainer +> kMapOfContainer = { + { + "items", + OfContainer::kItems + }, + { + "someItems", + OfContainer::kSomeItems + }, + { + "specialItems", + OfContainer::kSpecialItems + } +}; + +const std::size_t kPropertyCountOfWrapper = 1; + +const std::unordered_map< + std::string, + OfWrapper +> kMapOfWrapper = { + { + "container", + OfWrapper::kContainer + } +}; + +} // namespace properties + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> SomeItemFromSequence( + xml_common::ReaderMergingText& reader +) { + // region Initialization + + common::optional the_name; + + common::optional the_nickname; + + // endregion Initialization + + common::optional error( + ReadProperties< + properties::kPropertyCountOfSomeItem + >( + reader, + properties::kMapOfSomeItem, + L"ISomeItem", + [&]( + properties::OfSomeItem property + ) -> common::optional { + switch (property) { + case properties::OfSomeItem::kName: + return ReadInto( + the_name, + DeserializeWstring(reader) + ); + case properties::OfSomeItem::kNickname: + return ReadInto( + the_nickname, + DeserializeWstring(reader) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfSomeItem", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + // region Check required properties + + if (!the_name.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property name is missing" + ); + } + + // endregion Check required properties + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::SomeItem( + std::move(*the_name), + std::move(the_nickname) + ) + ), + common::nullopt + ); +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> SpecialItemFromSequence( + xml_common::ReaderMergingText& reader +) { + // region Initialization + + common::optional the_name; + + common::optional the_nickname; + + common::optional the_level; + + // endregion Initialization + + common::optional error( + ReadProperties< + properties::kPropertyCountOfSpecialItem + >( + reader, + properties::kMapOfSpecialItem, + L"ISpecialItem", + [&]( + properties::OfSpecialItem property + ) -> common::optional { + switch (property) { + case properties::OfSpecialItem::kName: + return ReadInto( + the_name, + DeserializeWstring(reader) + ); + case properties::OfSpecialItem::kNickname: + return ReadInto( + the_nickname, + DeserializeWstring(reader) + ); + case properties::OfSpecialItem::kLevel: + return ReadInto( + the_level, + DeserializeInt64(reader) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfSpecialItem", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + // region Check required properties + + if (!the_name.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property name is missing" + ); + } + + if (!the_level.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property level is missing" + ); + } + + // endregion Check required properties + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::SpecialItem( + std::move(*the_name), + std::move(*the_level), + std::move(the_nickname) + ) + ), + common::nullopt + ); +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> ContainerFromSequence( + xml_common::ReaderMergingText& reader +) { + // region Initialization + + common::optional< + std::vector< + std::shared_ptr + > + > the_items; + + common::optional< + std::vector< + std::shared_ptr + > + > the_some_items; + + common::optional< + std::vector< + std::shared_ptr + > + > the_special_items; + + // endregion Initialization + + common::optional error( + ReadProperties< + properties::kPropertyCountOfContainer + >( + reader, + properties::kMapOfContainer, + L"IContainer", + [&]( + properties::OfContainer property + ) -> common::optional { + switch (property) { + case properties::OfContainer::kItems: + return ReadInto( + the_items, + DeserializeList< + std::shared_ptr + >( + reader, + ItemFromElement + ) + ); + case properties::OfContainer::kSomeItems: + return ReadInto( + the_some_items, + DeserializeList< + std::shared_ptr + >( + reader, + SomeItemFromElement + ) + ); + case properties::OfContainer::kSpecialItems: + return ReadInto( + the_special_items, + DeserializeList< + std::shared_ptr + >( + reader, + SpecialItemFromElement + ) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfContainer", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + // region Check required properties + + if (!the_items.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property items is missing" + ); + } + + if (!the_some_items.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property someItems is missing" + ); + } + + // endregion Check required properties + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::Container( + std::move(*the_items), + std::move(*the_some_items), + std::move(the_special_items) + ) + ), + common::nullopt + ); +} + +template < + typename T, + typename std::enable_if< + std::is_base_of::value + >::type* +> +std::pair< + common::optional >, + common::optional +> WrapperFromSequence( + xml_common::ReaderMergingText& reader +) { + // region Initialization + + common::optional > the_container; + + // endregion Initialization + + common::optional error( + ReadProperties< + properties::kPropertyCountOfWrapper + >( + reader, + properties::kMapOfWrapper, + L"IWrapper", + [&]( + properties::OfWrapper property + ) -> common::optional { + switch (property) { + case properties::OfWrapper::kContainer: + return ReadInto( + the_container, + ContainerFromSequence< + types::IContainer + >(reader) + ); + default: + throw UnexpectedPropertyLiteralError( + "properties::OfWrapper", + property + ); + } + } + ) + ); + + if (error.has_value()) { + return NoInstanceAndDeserializationError< + std::shared_ptr + >( + std::move(*error) + ); + } + + // region Check required properties + + if (!the_container.has_value()) { + return NoInstanceAndDeserializationErrorWithCause< + std::shared_ptr + >( + L"The required property container is missing" + ); + } + + // endregion Check required properties + + return std::make_pair( + common::make_optional< + std::shared_ptr + >( + // NOTE (mristin): + // We deliberately do not use std::make_shared here to avoid an unnecessary + // upcast. + new types::Wrapper( + std::move(*the_container) + ) + ), + common::nullopt + ); +} + +template +common::expected< + ValueT, + DeserializationError +> DeserializeFrom( + std::istream& is, + const ReadingOptions& options, + const FromElementT& from_element +) { + xml_common::ReaderMergingText reader( + is, + options.additional_attributes, + options.buffer_size + ); + + reader.Initialize(); + if (reader.node().kind() == xml_common::NodeKind::Error) { + return common::make_unexpected( + DeserializationErrorFromReader(reader) + ); + } + + common::optional error = SkipBof(reader); + if (error.has_value()) { + return common::make_unexpected( + std::move(*error) + ); + } + + common::optional instance; + + std::tie( + instance, + error + ) = DeserializeNestedElement(reader, from_element); + + if (error.has_value()) { + return common::make_unexpected( + std::move(*error) + ); + } + + error = SkipWhitespace(reader); + if (error.has_value()) { + return common::make_unexpected( + std::move(*error) + ); + } + + error = CheckReaderAtEof(reader); + if (error.has_value()) { + return common::make_unexpected( + std::move(*error) + ); + } + + return std::move(*instance); +} + +common::expected< + std::shared_ptr, + DeserializationError +> From( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + ClassFromElement + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> ItemFrom( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + ItemFromElement + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> SomeItemFrom( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + SomeItemFromElement + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> SpecialItemFrom( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + SpecialItemFromElement + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> ContainerFrom( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + ContainerFromElement + ); +} + +common::expected< + std::shared_ptr, + DeserializationError +> WrapperFrom( + std::istream& is, + const ReadingOptions& options +) { + return DeserializeFrom< + std::shared_ptr + >( + is, + options, + WrapperFromElement + ); +} + +// endregion De-serialization + +// region Serialization + +// region SerializationException + +std::string RenderSerializationErrorMessage( + const std::wstring& cause, + const iteration::Path& path +) { + return common::WstringToUtf8( + common::Concat( + L"Serialization failed at ", + path.ToWstring(), + L": ", + cause + ) + ); +} + +SerializationException::SerializationException( + std::wstring cause +) : + cause_(std::move(cause)), + path_(), + msg_(RenderSerializationErrorMessage(cause, path_)) { + // Intentionally empty. +} + +SerializationException::SerializationException( + std::wstring cause, + iteration::Path path +) : + cause_(std::move(cause)), + path_(std::move(path)), + msg_(RenderSerializationErrorMessage(cause, path)) { + // Intentionally empty. +} + +const char* SerializationException::what() const noexcept { + return msg_.c_str(); +} + +const std::wstring& SerializationException::cause() const noexcept { + return cause_; +} + +const iteration::Path& SerializationException::path() const noexcept { + return path_; +} + +// endregion SerializationException + +common::optional WriteBool( + bool value, + xml_common::SelfClosingWriter& writer +) { + writer.SerializeBool(value); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +common::optional WriteInt64( + int64_t value, + xml_common::SelfClosingWriter& writer +) { + writer.SerializeInt64(value); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +common::optional WriteDouble( + double value, + xml_common::SelfClosingWriter& writer +) { + writer.SerializeDouble(value); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +common::optional WriteWstring( + const std::wstring& value, + xml_common::SelfClosingWriter& writer +) { + writer.SerializeWstring(value); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +common::optional WriteByteArray( + const std::vector& value, + xml_common::SelfClosingWriter& writer +) { + writer.SerializeByteArray(value); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +/** + * \brief Write \p value as an XML element named \p name. + * + * This is the only place where an element is framed. The element of a class, + * the `` of a list item and the positional ``, ``, etc. of + * a tuple item differ only in the name and in the content, so all of them + * come through here. + * + * \param name of the XML element + * \param value to be written between the tags + * \param writer to write to + * \param write_content writes \p value between the tags + * \return an error, if any + */ +template +common::optional WriteElement( + const char* name, + const T& value, + xml_common::SelfClosingWriter& writer, + const WriteT& write_content +) { + writer.StartElement(name); + if (writer.error().has_value()) { + return writer.move_error(); + } + + common::optional error( + write_content(value, writer) + ); + if (error.has_value()) { + return error; + } + + writer.StopElement(name); + if (writer.error().has_value()) { + return writer.move_error(); + } + + return common::nullopt; +} + +/** + * \brief Write \p value as the XML element of \p property. + * + * This is \ref WriteElement plus the one thing which a property knows and + * nothing beneath it does -- which property of the instance it is -- so that + * the path of the error is built as the stack unwinds. + * + * \param name of the XML element + * \param value of the property + * \param writer to write to + * \param property which the element stands for, for the path of the error + * \param write_content writes \p value between the tags + * \return an error, if any + */ +template +common::optional WriteProperty( + const char* name, + const T& value, + xml_common::SelfClosingWriter& writer, + iteration::Property property, + const WriteT& write_content +) { + common::optional error( + WriteElement(name, value, writer, write_content) + ); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique(property) + ); + } + + return error; +} + +/** + * \brief Write the instance behind \p value as the XML element of + * \p property. + * + * See the overload which takes the value itself for what is written and + * for the path of the error. + */ +template +common::optional WriteProperty( + const char* name, + const std::shared_ptr& value, + xml_common::SelfClosingWriter& writer, + iteration::Property property, + const WriteT& write_content +) { + return WriteProperty(name, *value, writer, property, write_content); +} + +/** + * \brief Write \p value as the XML element of \p property, or nothing at + * all if the property has not been given. + * + * See the overload which takes the value itself for what is written and + * for the path of the error. + */ +template +common::optional WriteProperty( + const char* name, + const common::optional& value, + xml_common::SelfClosingWriter& writer, + iteration::Property property, + const WriteT& write_content +) { + if (!value.has_value()) { + return common::nullopt; + } + + return WriteProperty(name, *value, writer, property, write_content); +} + +/** + * \brief Write \p list as the XML element of \p property, every item as + * an XML element of its own. + * + * An instance is self-describing -- the name of its XML element is its model + * type -- so an item needs no positional tag here. A value encoded as text + * does need one, which is why a list of values is written by a function of + * its own instead of by this one with the name of the item passed in. + * + * \param name of the XML element + * \param list of the instances + * \param writer to write to + * \param property which the element stands for, for the path of the error + * \param write_item writes an item as an XML element of its own + * \return an error, if any + */ +template +common::optional WriteListOfInstancesProperty( + const char* name, + const std::vector& list, + xml_common::SelfClosingWriter& writer, + iteration::Property property, + const WriteItemT& write_item +) { + return WriteProperty( + name, + list, + writer, + property, + [&write_item]( + const std::vector& a_list, + xml_common::SelfClosingWriter& a_writer + ) -> common::optional { + for (size_t i = 0; i < a_list.size(); ++i) { + common::optional error( + write_item(a_list[i], a_writer) + ); + + if (error.has_value()) { + error->path.segments.emplace_front( + common::make_unique(i) + ); + + return error; + } + } + + return common::nullopt; + } + ); +} + +/** + * \brief Write \p list as the XML element of \p property, or nothing at all + * if the property has not been given. + * + * See the overload which takes the list itself for what is written and + * for the path of the error. + */ +template +common::optional WriteListOfInstancesProperty( + const char* name, + const common::optional >& list, + xml_common::SelfClosingWriter& writer, + iteration::Property property, + const WriteItemT& write_item +) { + if (!list.has_value()) { + return common::nullopt; + } + + return WriteListOfInstancesProperty( + name, + *list, + writer, + property, + write_item + ); +} + +/** + * \brief Serialize \p that instance by dispatching to the appropriate concrete + * serialization function. + * + * \param that instance to be serialized + * \param writer to be write to + * \return error, if any + */ +common::optional SerializeItemAsElement( + const types::IItem& that, + xml_common::SelfClosingWriter& writer +); + +/** @copybrief SerializeItemAsElement(const types::IItem&, xml_common::SelfClosingWriter& */ +common::optional SerializeItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +); + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeSomeItemAsSequence( + const types::ISomeItem& that, + xml_common::SelfClosingWriter& writer +); + +/** + * \brief Serialize \p that instance by dispatching to the appropriate concrete + * serialization function. + * + * \param that instance to be serialized + * \param writer to be write to + * \return error, if any + */ +common::optional SerializeSomeItemAsElement( + const types::ISomeItem& that, + xml_common::SelfClosingWriter& writer +); + +/** @copybrief SerializeSomeItemAsElement(const types::ISomeItem&, xml_common::SelfClosingWriter& */ +common::optional SerializeSomeItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +); + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeSpecialItemAsSequence( + const types::ISpecialItem& that, + xml_common::SelfClosingWriter& writer +); + +/** + * Serialize \p that instance to an XML element + * ``. + * + * \param that instance to be serialized + * \return an error, if any + */ +common::optional SerializeSpecialItemAsElement( + const types::ISpecialItem& that, + xml_common::SelfClosingWriter& writer +); + +/** @copybrief SerializeSpecialItemAsElement(const types::ISpecialItem&, xml_common::SelfClosingWriter& */ +common::optional SerializeSpecialItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +); + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeContainerAsSequence( + const types::IContainer& that, + xml_common::SelfClosingWriter& writer +); + +/** + * Serialize \p that instance to an XML element + * ``. + * + * \param that instance to be serialized + * \return an error, if any + */ +common::optional SerializeContainerAsElement( + const types::IContainer& that, + xml_common::SelfClosingWriter& writer +); + +/** @copybrief SerializeContainerAsElement(const types::IContainer&, xml_common::SelfClosingWriter& */ +common::optional SerializeContainerPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +); + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeWrapperAsSequence( + const types::IWrapper& that, + xml_common::SelfClosingWriter& writer +); + +/** + * Serialize \p that instance to an XML element + * ``. + * + * \param that instance to be serialized + * \return an error, if any + */ +common::optional SerializeWrapperAsElement( + const types::IWrapper& that, + xml_common::SelfClosingWriter& writer +); + +/** @copybrief SerializeWrapperAsElement(const types::IWrapper&, xml_common::SelfClosingWriter& */ +common::optional SerializeWrapperPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +); + +common::optional SerializeItemAsElement( + const types::IItem& that, + xml_common::SelfClosingWriter& writer +) { + // NOTE (mristin): + // The dynamic casts are necessary due to virtual inheritance. Otherwise, + // we would have used static casts. + + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return SerializeSomeItemAsElement( + dynamic_cast< + const types::ISomeItem& + >(that), + writer + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItemAsElement( + dynamic_cast< + const types::ISpecialItem& + >(that), + writer + ); + default: + throw std::invalid_argument( + common::Concat( + "Invalid model type: ", + stringification::to_string(that.model_type()) + ) + ); + }; +} + +common::optional SerializeItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +) { + return SerializeItemAsElement(*that, writer); +} + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeSomeItemAsSequence( + const types::ISomeItem& that, + xml_common::SelfClosingWriter& writer +) { + common::optional error; + + error = WriteProperty( + "name", + that.name(), + writer, + iteration::Property::kName, + WriteWstring + ); + if (error.has_value()) { + return error; + } + + error = WriteProperty( + "nickname", + that.nickname(), + writer, + iteration::Property::kNickname, + WriteWstring + ); + if (error.has_value()) { + return error; + } + + return common::nullopt; +} + +/** + * Serialize \p that instance to an XML element + * ``. + * + * No dispatch is performed in this function. It is expected that you call + * \ref SerializeSomeItemAsElement, which will then dispatch into this function. + * + * \param that instance to be serialized + * \param writer to write to + * \return an error, if any + */ +common::optional SerializeConcreteSomeItemAsElement( + const types::ISomeItem& that, + xml_common::SelfClosingWriter& writer +) { + return WriteElement( + "someItem", + that, + writer, + SerializeSomeItemAsSequence + ); +} + +common::optional SerializeSomeItemAsElement( + const types::ISomeItem& that, + xml_common::SelfClosingWriter& writer +) { + // NOTE (mristin): + // The dynamic casts are necessary due to virtual inheritance. Otherwise, + // we would have used static casts. + + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return SerializeConcreteSomeItemAsElement( + that, + writer + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItemAsElement( + dynamic_cast< + const types::ISpecialItem& + >(that), + writer + ); + default: + throw std::invalid_argument( + common::Concat( + "Invalid model type: ", + stringification::to_string(that.model_type()) + ) + ); + }; +} + +common::optional SerializeSomeItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +) { + return SerializeSomeItemAsElement(*that, writer); +} + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeSpecialItemAsSequence( + const types::ISpecialItem& that, + xml_common::SelfClosingWriter& writer +) { + common::optional error; + + error = WriteProperty( + "name", + that.name(), + writer, + iteration::Property::kName, + WriteWstring + ); + if (error.has_value()) { + return error; + } + + error = WriteProperty( + "nickname", + that.nickname(), + writer, + iteration::Property::kNickname, + WriteWstring + ); + if (error.has_value()) { + return error; + } + + error = WriteProperty( + "level", + that.level(), + writer, + iteration::Property::kLevel, + WriteInt64 + ); + if (error.has_value()) { + return error; + } + + return common::nullopt; +} + +common::optional SerializeSpecialItemAsElement( + const types::ISpecialItem& that, + xml_common::SelfClosingWriter& writer +) { + return WriteElement( + "specialItem", + that, + writer, + SerializeSpecialItemAsSequence + ); +} + +common::optional SerializeSpecialItemPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +) { + return SerializeSpecialItemAsElement(*that, writer); +} + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeContainerAsSequence( + const types::IContainer& that, + xml_common::SelfClosingWriter& writer +) { + common::optional error; + + error = WriteListOfInstancesProperty( + "items", + that.items(), + writer, + iteration::Property::kItems, + SerializeItemPtrAsElement + ); + if (error.has_value()) { + return error; + } + + error = WriteListOfInstancesProperty( + "someItems", + that.some_items(), + writer, + iteration::Property::kSomeItems, + SerializeSomeItemPtrAsElement + ); + if (error.has_value()) { + return error; + } + + error = WriteListOfInstancesProperty( + "specialItems", + that.special_items(), + writer, + iteration::Property::kSpecialItems, + SerializeSpecialItemPtrAsElement + ); + if (error.has_value()) { + return error; + } + + return common::nullopt; +} + +common::optional SerializeContainerAsElement( + const types::IContainer& that, + xml_common::SelfClosingWriter& writer +) { + return WriteElement( + "container", + that, + writer, + SerializeContainerAsSequence + ); +} + +common::optional SerializeContainerPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +) { + return SerializeContainerAsElement(*that, writer); +} + +/** + * \brief Serialize \p that instance as a sequence of XML elements. + * + * Each XML element corresponds to a property. + * + * \param that instance to be serialized + * \param writer to write to + * \return error, if any + */ +common::optional SerializeWrapperAsSequence( + const types::IWrapper& that, + xml_common::SelfClosingWriter& writer +) { + common::optional error; + + error = WriteProperty( + "container", + that.container(), + writer, + iteration::Property::kContainer, + SerializeContainerAsSequence + ); + if (error.has_value()) { + return error; + } + + return common::nullopt; +} + +common::optional SerializeWrapperAsElement( + const types::IWrapper& that, + xml_common::SelfClosingWriter& writer +) { + return WriteElement( + "wrapper", + that, + writer, + SerializeWrapperAsSequence + ); +} + +common::optional SerializeWrapperPtrAsElement( + const std::shared_ptr& that, + xml_common::SelfClosingWriter& writer +) { + return SerializeWrapperAsElement(*that, writer); +} + +common::optional WriteClass( + const types::IClass& that, + xml_common::SelfClosingWriter& writer +) { + // NOTE (mristin): + // The dynamic casts are necessary due to virtual inheritance. Otherwise, + // we would have used static casts. + + switch (that.model_type()) { + case types::ModelType::kSomeItem: + return SerializeSomeItemAsElement( + dynamic_cast< + const types::ISomeItem& + >(that), + writer + ); + case types::ModelType::kSpecialItem: + return SerializeSpecialItemAsElement( + dynamic_cast< + const types::ISpecialItem& + >(that), + writer + ); + case types::ModelType::kContainer: + return SerializeContainerAsElement( + dynamic_cast< + const types::IContainer& + >(that), + writer + ); + case types::ModelType::kWrapper: + return SerializeWrapperAsElement( + dynamic_cast< + const types::IWrapper& + >(that), + writer + ); + default: + throw std::invalid_argument( + common::Concat( + "Invalid model type: ", + stringification::to_string(that.model_type()) + ) + ); + }; +} + +void Serialize( + const types::IClass& that, + const WritingOptions& options, + std::ostream& os +) { + if (options.write_declaration) { + os << "\n"; + if (os.bad()) { + throw SerializationException( + xml_common::kTheOutputStreamIsInABadState + ); + } + } + + // NOTE (mristin): + // The namespace is declared on the root element and on no other one, so we + // hand it to the writer instead of asking at every single element whether it + // is the root. The writer takes the attributes at the very first element and + // leaves nothing behind. + xml_common::SelfClosingWriter writer( + os, + options.write_namespace + ? " xmlns=\"https://dummy.com\"" + : "" + ); + + common::optional error( + WriteClass(that, writer) + ); + + if (error.has_value()) { + throw SerializationException( + std::move(error->cause), + std::move(error->path) + ); + } +} + +// endregion Serialization + +} // namespace xmlization +} // namespace dummy + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/catch_main.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/catch_main.cpp new file mode 100644 index 000000000..bcec31566 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/catch_main.cpp @@ -0,0 +1,12 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Define the main function of Catch2 for all the tests. + * + * Compiling the implementation of Catch2 takes long, so we compile it only once + * in this translation unit, and link all the tests against it. + */ + +#define CATCH_CONFIG_MAIN +#include diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.cpp new file mode 100644 index 000000000..fd536c267 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.cpp @@ -0,0 +1,422 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace our = dummy; + +namespace fs = std::filesystem; + +namespace test { +namespace common { + +static std::optional GetEnv(const char *variable_name) { + // NOTE (empwilli): MSVC complains about getenv being unsafe. + // This should not be an issue as we don't run our tests in parallel, though, + // so we use getenv_s in Windows. However, this function is a Microsoft + // extension. +#ifdef _WIN32 + char buffer[256]; + size_t len; + errno_t error = getenv_s( + &len, buffer, sizeof(buffer), variable_name + ); + if (error != 0) { + throw std::runtime_error( + our::common::Concat( + "The getenv_s returned an error code ", + std::to_string(error), + " when asking for the variable ", + variable_name + ) + ); + } + + if (len == 0) { + return std::nullopt; + } + + if (buffer[len - 1] != 0) { + throw std::logic_error( + our::common::Concat( + "Expected the last byte of buffer for getenv_s on ", + variable_name, + " to be zero, but got ", + std::to_string(static_cast(buffer[len - 1])) + ) + ); + } + + return std::optional { std::string(buffer, len - 1) }; +#else + auto value = getenv(variable_name); + + if (value == nullptr) { + return std::nullopt; + } + + return std::optional { std::string(value) }; +#endif +} + +bool DetermineRecordMode() { + const char* variable_name = "DUMMY_TEST_RECORD_MODE"; + + auto result = GetEnv(variable_name); + + if (!result.has_value()) + { + return false; + } + + static const std::set hot_value_set{ + "on", "ON", "On", + "true", "TRUE", "True", + "1", + "yes", "Yes", "YES" + }; + return hot_value_set.find(result.value()) != hot_value_set.end(); +} + +std::filesystem::path DetermineTestDataDir() { + const char* variable_name = "DUMMY_TEST_DATA_DIR"; + + auto result = GetEnv(variable_name); + + if (!result.has_value()) + { + throw std::runtime_error( + our::common::Concat( + "The environment variable ", + variable_name, + " has not been set." + ) + ); + } + + return fs::path(result.value()); +} + +/** + * Write `text` to the given `path` encoded as UTF-8. + */ +void MustWriteWstringAsUtf8( + const std::filesystem::path& path, + const std::wstring& text +) { + std::ofstream ofs(path, std::ios::binary); + + const std::string encoded = our::common::WstringToUtf8(text); + ofs.write(encoded.data(), encoded.size()); + + if (ofs.fail()) { + throw std::runtime_error( + our::common::Concat( + "Failed to write to ", + path.string(), + "; the fail bit of the file stream is set" + ) + ); + } + + if (ofs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to write to ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } +} + +/** + * Read the content of the file `path` and decode it from UTF-8. + */ +std::wstring MustReadWstringAsUtf8( + const std::filesystem::path& path +) { + std::ifstream ifs(path, std::ios::binary); + ifs.seekg(0, std::ios::end); + std::streamoff size = ifs.tellg(); + if (size < 0) { + throw std::runtime_error( + our::common::Concat( + "Unexpected negative size of ", + path.string(), + ": ", + std::to_string(size) + ) + ); + } + + std::string buffer(static_cast(size), ' '); + ifs.seekg(0); + ifs.read(&buffer[0], size); + + if (ifs.fail()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read from ", + path.string(), + "; the fail bit of the file stream is set" + ) + ); + } + + if (ifs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read from ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } + + try { + return our::common::Utf8ToWstring(buffer); + } catch (const std::exception& exception) { + throw std::runtime_error( + our::common::Concat( + "Failed to de-code ", + path.string(), + " as UTF-8: ", + exception.what() + ) + ); + } +} + +/** + * Write `text` to the given `path` as binary. + */ +void MustWriteString( + const std::filesystem::path& path, + const std::string& text +) { + std::ofstream ofs(path, std::ios::binary); + + ofs.write(text.data(), text.size()); + + if (ofs.fail()) { + throw std::runtime_error( + our::common::Concat( + "Failed to write to ", + path.string(), + "; the fail bit of the file stream is set" + ) + ); + } + + if (ofs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to write to ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } +} + +/** + * Read the content of the file `path` as binary. + */ +std::string MustReadString( + const std::filesystem::path& path +) { + std::ifstream ifs(path, std::ios::binary); + ifs.seekg(0, std::ios::end); + std::streamoff size = ifs.tellg(); + if (size < 0) { + throw std::runtime_error( + our::common::Concat( + "Unexpected negative size of ", + path.string(), + ": ", + std::to_string(size) + ) + ); + } + + std::string buffer(static_cast(size), ' '); + ifs.seekg(0); + ifs.read(&buffer[0], size); + + if (ifs.fail()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read from ", + path.string(), + "; the fail bit of the file stream is set" + ) + ); + } + + if (ifs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read from ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } + + return buffer; +} + +std::wstring TraceMark( + const dummy::types::IClass& that +) { + const std::wstring model_type = our::wstringification::to_wstring( + that.model_type() + ); + return model_type; +} + +bool StringEndsWith( + const std::string& text, + const std::string& suffix +) { + if (text.size() < suffix.size()) { + return false; + } + + return ( + text.substr(text.size() - suffix.size(), suffix.size()) + == suffix + ); +} + +std::deque FindFilesBySuffixRecursively( + const std::filesystem::path& root, + const std::string& suffix +) { + if (!fs::exists(root)) { + return std::deque(); + } + + if (!fs::is_directory(root)) { + throw std::runtime_error( + our::common::Concat( + "Expected to search for files with suffix recursively in ", + root.string(), + ", but it is not a directory" + ) + ); + } + + std::deque result; + + for ( + const fs::directory_entry& entry + : fs::recursive_directory_iterator(root) + ) { + if ( + StringEndsWith( + entry.path().filename().string(), + suffix + ) + ) { + result.push_back(entry.path()); + } + } + + std::sort(result.begin(), result.end()); + + return result; +} + +std::deque ListSubdirectories( + const std::filesystem::path& root +) { + if (!fs::exists(root)) { + throw std::runtime_error( + our::common::Concat( + "The root directory which you wanted to list for subdirectories " + "does not exist: ", + root.string() + ) + ); + } + + if (!fs::is_directory(root)) { + throw std::runtime_error( + our::common::Concat( + "The path that you specified as a root directory which you wanted to " + "list for subdirectories is not a directory: ", + root.string() + ) + ); + } + + std::deque result; + + for ( + const fs::directory_entry& entry + : fs::directory_iterator(root) + ) { + if (fs::is_directory(entry.path())) { + result.push_back(entry.path()); + } + } + + std::sort(result.begin(), result.end()); + return result; +} + +void AssertContentEqualsExpectedOrRecord( + const std::string& content, + const std::filesystem::path& path +) { + const bool record_mode = DetermineRecordMode(); + if (record_mode) { + fs::create_directories(path.parent_path()); + + MustWriteString(path, content); + } else { + INFO( + our::common::Concat( + "Expected to compare the content against " + "the recorded file: ", + path.string(), + ", but the file does not exist. If you want to re-record, " + "please set the environment variable DUMMY_TEST_RECORD_MODE " + "to 'ON'" + ) + ) + REQUIRE(std::filesystem::exists(path)); + + const std::string expected = MustReadString(path); + INFO( + our::common::Concat( + "Got unexpected content, which should have been equal " + "to the content of the file: ", + path.string() + ) + ) + REQUIRE(content == expected); + } +} + +} // namespace common +} // namespace test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.hpp new file mode 100644 index 000000000..fa5b4f8e5 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common.hpp @@ -0,0 +1,142 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#ifndef TEST_COMMON_GUARD_ +#define TEST_COMMON_GUARD_ + +#include + +#include + +#include +#include + +/** +* Provide common functionalities shared across the tests. +*/ +namespace test { +namespace common { + +/** + * Set to true if we run tests in the record mode according to the environment + * variable `DUMMY_TEST_RECORD_MODE`. + * + * \remark It is tedious to record manually all the expected error messages. Therefore + * we include this variable to steer the automatic recording. We intentionally + * intertwine the recording code with the test code to keep them close to each other + * so that they are easier to maintain. + */ +bool DetermineRecordMode(); + +/** + * Point to the directory with the test data according to the environment + * variable `DUMMY_TEST_DATA_DIR`. + */ +std::filesystem::path DetermineTestDataDir(); + +/** + * Represent `that` instance as a human-readable line of an iteration trace. + */ +std::wstring TraceMark( + const dummy::types::IClass& that +); + +/** + * Find files beneath the `root` recursively which have the `suffix`. + * + * If the `root` does not exist, return an empty deque. + * + * If the `root` is not a directory, throw a runtime error. + * + * The files are sorted. + */ +std::deque FindFilesBySuffixRecursively( + const std::filesystem::path& root, + const std::string& suffix +); + +/** + * List non-recursively the subdirectories contained in the \p root. + * + * @param root directory that you want to list + * @return subdirectories beneath \p root, sorted + */ +std::deque ListSubdirectories( + const std::filesystem::path& root +); + +/** + * Check that the content coincides with the file or re-record if in record mode. + */ +void AssertContentEqualsExpectedOrRecord( + const std::string& content, + const std::filesystem::path& path +); + +template +std::string JoinStrings(const T& container, const std::string& delimiter) { + static_assert(std::is_same::value); + + size_t size = 0; + for (const std::string& part : container + ) { + size += part.size(); + } + size += delimiter.size() * (container.size() - 1); + + std::string result; + result.reserve(size); + + auto it = container.begin(); + for (size_t i = 0; i < container.size() - 1; ++i) { + result.append(*it); + result.append(delimiter); + ++it; + } + + result.append(*it); + + return result; +} + +template +std::wstring JoinWstrings(const T& container, const std::wstring& delimiter) { + if (container.size() == 0) { + return L""; + } + + static_assert(std::is_same::value); + + size_t size = 0; + for (const std::wstring& part : container + ) { + size += part.size(); + } + size += delimiter.size() * (container.size() - 1); + + std::wstring result; + result.reserve(size); + + auto it = container.begin(); + for (size_t i = 0; i < container.size() - 1; ++i) { + result.append(*it); + result.append(delimiter); + ++it; + } + + result.append(*it); + + return result; +} + +std::string MustReadString( + const std::filesystem::path& path +); + +} // namespace common +} // namespace test + +#endif // TEST_COMMON_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.cpp new file mode 100644 index 000000000..7cd16d582 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.cpp @@ -0,0 +1,335 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_examples.hpp" +#include "./common_xmlization.hpp" + +#include +#include + +#include + +namespace our = dummy; + +namespace test { +namespace common { +namespace examples { + +template +struct StaticTypeName; + +template<> +struct StaticTypeName< + our::types::ISomeItem +> { + static const char* name; +}; +const char* StaticTypeName< + our::types::ISomeItem +>::name = "ISomeItem"; + +template<> +struct StaticTypeName< + our::types::ISpecialItem +> { + static const char* name; +}; +const char* StaticTypeName< + our::types::ISpecialItem +>::name = "ISpecialItem"; + +template<> +struct StaticTypeName< + our::types::IContainer +> { + static const char* name; +}; +const char* StaticTypeName< + our::types::IContainer +>::name = "IContainer"; + +template<> +struct StaticTypeName< + our::types::IWrapper +> { + static const char* name; +}; +const char* StaticTypeName< + our::types::IWrapper +>::name = "IWrapper"; + +std::shared_ptr< + our::types::ISomeItem +> LoadSomeItem( + const std::filesystem::path& path +) { + + + std::shared_ptr< + our::types::IClass + > abstract = test::common::xmlization::MustReadInstance( + path + ); + + std::shared_ptr< + our::types::ISomeItem + > instance( + std::dynamic_pointer_cast< + our::types::ISomeItem + >( + abstract + ) + ); + + if (instance == nullptr) { + throw std::runtime_error( + our::common::Concat( + "Failed to cast the instance to ISomeItem from ", + path.string() + ) + ); + } + + return instance; +} + +std::shared_ptr< + our::types::ISomeItem +> LoadMinSomeItem() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "someItem" + / "minimal.xml" + ); + + return LoadSomeItem( + path + ); +} + +std::shared_ptr< + our::types::ISomeItem +> LoadMaxSomeItem() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "someItem" + / "maximal.xml" + ); + + return LoadSomeItem( + path + ); +} + +std::shared_ptr< + our::types::ISpecialItem +> LoadSpecialItem( + const std::filesystem::path& path +) { + + + std::shared_ptr< + our::types::IClass + > abstract = test::common::xmlization::MustReadInstance( + path + ); + + std::shared_ptr< + our::types::ISpecialItem + > instance( + std::dynamic_pointer_cast< + our::types::ISpecialItem + >( + abstract + ) + ); + + if (instance == nullptr) { + throw std::runtime_error( + our::common::Concat( + "Failed to cast the instance to ISpecialItem from ", + path.string() + ) + ); + } + + return instance; +} + +std::shared_ptr< + our::types::ISpecialItem +> LoadMinSpecialItem() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "specialItem" + / "minimal.xml" + ); + + return LoadSpecialItem( + path + ); +} + +std::shared_ptr< + our::types::ISpecialItem +> LoadMaxSpecialItem() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "specialItem" + / "maximal.xml" + ); + + return LoadSpecialItem( + path + ); +} + +std::shared_ptr< + our::types::IContainer +> LoadContainer( + const std::filesystem::path& path +) { + + + std::shared_ptr< + our::types::IClass + > abstract = test::common::xmlization::MustReadInstance( + path + ); + + std::shared_ptr< + our::types::IContainer + > instance( + std::dynamic_pointer_cast< + our::types::IContainer + >( + abstract + ) + ); + + if (instance == nullptr) { + throw std::runtime_error( + our::common::Concat( + "Failed to cast the instance to IContainer from ", + path.string() + ) + ); + } + + return instance; +} + +std::shared_ptr< + our::types::IContainer +> LoadMinContainer() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "container" + / "minimal.xml" + ); + + return LoadContainer( + path + ); +} + +std::shared_ptr< + our::types::IContainer +> LoadMaxContainer() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "container" + / "maximal.xml" + ); + + return LoadContainer( + path + ); +} + +std::shared_ptr< + our::types::IWrapper +> LoadWrapper( + const std::filesystem::path& path +) { + + + std::shared_ptr< + our::types::IClass + > abstract = test::common::xmlization::MustReadInstance( + path + ); + + std::shared_ptr< + our::types::IWrapper + > instance( + std::dynamic_pointer_cast< + our::types::IWrapper + >( + abstract + ) + ); + + if (instance == nullptr) { + throw std::runtime_error( + our::common::Concat( + "Failed to cast the instance to IWrapper from ", + path.string() + ) + ); + } + + return instance; +} + +std::shared_ptr< + our::types::IWrapper +> LoadMinWrapper() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "wrapper" + / "minimal.xml" + ); + + return LoadWrapper( + path + ); +} + +std::shared_ptr< + our::types::IWrapper +> LoadMaxWrapper() { + const std::filesystem::path path( + test::common::DetermineTestDataDir() + / "Xml" + / "Expected" + / "wrapper" + / "maximal.xml" + ); + + return LoadWrapper( + path + ); +} + +} // namespace examples +} // namespace common +} // namespace test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.hpp new file mode 100644 index 000000000..4b6a6a395 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_examples.hpp @@ -0,0 +1,88 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** +* Load minimal and maximal examples from an XML file. +*/ + +#ifndef TEST_COMMON_EXAMPLES_GUARD_ +#define TEST_COMMON_EXAMPLES_GUARD_ + +#include + +namespace test { +namespace common { +namespace examples { + +/** + * Load a minimal example of ISomeItem by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::ISomeItem +> LoadMinSomeItem(); + +/** + * Load a maximal example of ISomeItem by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::ISomeItem +> LoadMaxSomeItem(); + +/** + * Load a minimal example of ISpecialItem by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::ISpecialItem +> LoadMinSpecialItem(); + +/** + * Load a maximal example of ISpecialItem by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::ISpecialItem +> LoadMaxSpecialItem(); + +/** + * Load a minimal example of IContainer by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::IContainer +> LoadMinContainer(); + +/** + * Load a maximal example of IContainer by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::IContainer +> LoadMaxContainer(); + +/** + * Load a minimal example of IWrapper by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::IWrapper +> LoadMinWrapper(); + +/** + * Load a maximal example of IWrapper by + * de-serializing it from an XML file. + */ +std::shared_ptr< + dummy::types::IWrapper +> LoadMaxWrapper(); + +} // namespace examples +} // namespace common +} // namespace test + +#endif // TEST_COMMON_EXAMPLES_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.cpp new file mode 100644 index 000000000..5f673d146 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.cpp @@ -0,0 +1,93 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common_jsonization.hpp" + +#include + +#include + +namespace our = dummy; + +namespace test { +namespace common { +namespace jsonization { + +nlohmann::json MustReadJson(const std::filesystem::path& path) { + std::ifstream ifs(path); + + nlohmann::json result; + try { + result = nlohmann::json::parse(ifs); + } catch (nlohmann::json::parse_error& exception) { + throw std::runtime_error( + our::common::Concat( + "Failed to read JSON from ", + path.string(), + ": ", + exception.what() + ) + ); + } + + if (ifs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read JSON from ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } + + if (ifs.fail()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read JSON from ", + path.string(), + "; the fail bit of the file stream is set" + ) + ); + } + + if (!ifs.eof()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read JSON from ", + path.string(), + "; the EOF bit is not set meaning that we did not parse all the content" + ) + ); + } + + return result; +} + +std::optional CompareJsons( + const nlohmann::json& that, + const nlohmann::json& other +) { + const nlohmann::json patch = nlohmann::json::diff(that, other); + + if (!patch.is_array()) { + throw std::logic_error( + our::common::Concat( + "Expected the patch to be an array, but got ", + patch.type_name() + ) + ); + } + + if (patch.size() != 0) { + return nlohmann::to_string(patch); + } + + return std::nullopt; +} + +} // namespace jsonization +} // namespace common +} // namespace test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.hpp new file mode 100644 index 000000000..029a98621 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_jsonization.hpp @@ -0,0 +1,42 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Provide methods which are used throughout the jsonization tests. + */ +#ifndef TEST_COMMON_JSONIZATION_GUARD_ +#define TEST_COMMON_JSONIZATION_GUARD_ + +#include + +#include +#include +#include + +namespace test { +namespace common { +namespace jsonization { + +/** + * Read the content of the `path` and parse it as JSON. + */ +nlohmann::json MustReadJson(const std::filesystem::path& path); + +/** + * Compare two JSON values. + * + * Return the JSON patch, if there is any difference. + */ +std::optional CompareJsons( + const nlohmann::json& that, + const nlohmann::json& other +); + +} // namespace jsonization +} // namespace common +} // namespace test + +#endif // TEST_COMMON_JSONIZATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.cpp new file mode 100644 index 000000000..42aceb674 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.cpp @@ -0,0 +1,637 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common_xmlization.hpp" +#include "./common.hpp" + +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +#include + +namespace our = dummy; + +namespace test { +namespace common { +namespace xmlization { + +std::shared_ptr< + dummy::types::IClass +> MustReadInstance( + const std::filesystem::path& path +) { + std::ifstream ifs(path); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > instance = our::xmlization::From( + ifs + ); + + if (ifs.bad()) { + throw std::runtime_error( + our::common::Concat( + "Failed to read XML from ", + path.string(), + "; the bad bit of the file stream is set" + ) + ); + } + + if (!instance.has_value()) { + throw std::runtime_error( + our::common::Concat( + "Failed to parse the instance from the XML file ", + path.string(), + ": ", + our::common::WstringToUtf8(instance.error().path.ToWstring()), + ": ", + our::common::WstringToUtf8(instance.error().cause) + ) + ); + } + + return *instance; +} + +namespace canonicalizer { + +struct INode { + std::string text; + + INode( + std::string a_text + ) : + text(a_text) { + // Intentionally empty. + } + + virtual std::unique_ptr Copy() const = 0; + virtual ~INode() = default; +}; + +struct StartNode : INode { + std::string name; + + StartNode( + std::string a_name, + std::string a_text + ) : + INode(std::move(a_text)), + name(std::move(a_name)) { + // Intentionally empty. + } + + std::unique_ptr Copy() const override { + return std::make_unique(*this); + } + + ~StartNode() override = default; +}; + +struct TextNode : INode { + TextNode( + std::string a_text + ) : INode(std::move(a_text)) { + // Intentionally empty. + } + + std::unique_ptr Copy() const override { + return std::make_unique(*this); + } + + ~TextNode() override = default; +}; + +struct StopNode : INode { + std::string name; + + StopNode( + std::string a_name, + std::string a_text + ) : + INode(std::move(a_text)), + name(std::move(a_name)) { + // Intentionally empty. + } + + std::unique_ptr Copy() const override { + return std::make_unique(*this); + } + + ~StopNode() override = default; +}; + +/** + * Structure the data passed over to Expat XML reader. + */ +struct OurData { + XML_Parser parser; + std::optional error; + + std::deque > nodes; + + OurData( + XML_Parser a_parser + ) : + parser(a_parser) { + // Intentionally empty. + } +}; // struct OurData + +our::common::expected< + std::string, + std::string +> EscapeForXmlAttribute(const std::string_view& text) { + // NOTE (mristin): + // See: https://stackoverflow.com/questions/19766669/which-characters-are-permitted-in-xml-attributes + // + // In addition, we assume that we will always surround an attribute by a double-quote. + + size_t size = 0; + for (char character : text + ) { + switch (character) { + case '<':size += sizeof("<"); + break; + case '"':size += sizeof("""); + break; + case '\0': + return our::common::unexpected( + "Unexpected zero character in the text to be escaped " + "for XML attribute. The XML format does not allow zero characters, " + "not even in escaped form." + ); + default:++size; + } + } + + std::string result; + result.reserve(size); + + for (char character : text + ) { + switch (character) { + case '<':result.append("<"); + break; + case '"':result.append("""); + break; + default:result.push_back(character); + } + } + + return result; +} + +our::common::expected< + std::string, + std::string +> EscapeForXmlText(const std::string_view& text) { + size_t size = 0; + for (char character : text + ) { + switch (character) { + case '<':size += sizeof("<"); + break; + case '>':size += sizeof(">"); + break; + case '&':size += sizeof("&"); + break; + case '\'':size += sizeof("'"); + break; + case '"':size += sizeof("""); + break; + case '\0': + return our::common::unexpected( + "Unexpected zero character in the text to be escaped " + "for XML attribute. The XML format does not allow zero characters, " + "not even in escaped form." + ); + default:++size; + } + } + + if (text.size() == size) { + return std::string(text); + } + + std::string result; + result.reserve(size); + + for (char character : text + ) { + switch (character) { + case '<':result.append("<"); + break; + case '>':result.append(">"); + break; + case '&':result.append("&"); + break; + case '\'':result.append("'"); + break; + case '"':result.append("""); + break; + default:result.push_back(character); + } + } + + return result; +} + +void XMLCALL OnStartElement( + void* user_data, + const char* name, + const char* attributes[] +) { + auto our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->error.has_value()) { + return; + } + + std::stringstream ss; + ss << "<" << name; + + for (size_t i = 0; attributes[i] != nullptr; i += 2 + ) { + const char* attribute_name = attributes[i]; + const char* attribute_value = attributes[i + 1]; + + our::common::expected< + std::string, + std::string + > escaped = EscapeForXmlAttribute( + std::string_view( + attribute_value, + strlen(attribute_value) + ) + ); + if (!escaped.has_value()) { + our_data->error = escaped.error(); + XML_StopParser(our_data->parser, false); + return; + } + + ss + << " " + << attribute_name << "=\"" + << *escaped + << "\""; + } + + ss << ">"; + + our_data->nodes.emplace_back( + our::common::make_unique( + name, + ss.str() + ) + ); +} + +void XMLCALL OnStopElement( + void* user_data, + const char* name +) { + auto* our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->error.has_value()) { + return; + } + + our_data->nodes.emplace_back( + our::common::make_unique( + name, + our::common::Concat( + "" + ) + ) + ); +} + +void XMLCALL OnText( + void* user_data, + const char* val, + int len +) { + auto our_data = static_cast(user_data); + + // NOTE (mristin): + // Since Expat continues parsing and adding nodes even if the parsing is + // suspended (see the documentation of `XML_StopParser`), we have to ignore + // any further events. + if (our_data->error.has_value()) { + return; + } + + our::common::expected< + std::string, + std::string + > escaped = EscapeForXmlText(std::string_view(val, len)); + + if (!escaped.has_value()) { + our_data->error = std::move(escaped.error()); + XML_StopParser(our_data->parser, false); + return; + } + + our_data->nodes.emplace_back( + our::common::make_unique( + std::move(*escaped) + ) + ); +} + +std::deque > MergeTextNodes( + const std::deque >& nodes +) { + std::deque parts; + + std::deque > result; + + for (const std::unique_ptr& node : nodes + ) { + auto start_node = dynamic_cast(node.get()); + auto stop_node = dynamic_cast(node.get()); + + if (start_node != nullptr || stop_node != nullptr) { + if (!parts.empty()) { + result.emplace_back( + std::make_unique( + test::common::JoinStrings(parts, "") + ) + ); + parts.clear(); + } + + result.emplace_back(node->Copy()); + + continue; + } + + auto text_node = dynamic_cast(node.get()); + if (text_node != nullptr) { + parts.push_back(text_node->text); + } + } + + if (!parts.empty()) { + result.emplace_back( + std::make_unique( + test::common::JoinStrings(parts, "") + ) + ); + } + + return result; +} + +bool IsWhitespace(const std::string& text) { + for (const char character : text + ) { + switch (character) { + case '\n':continue; + case '\t':continue; + case '\r':continue; + case ' ':continue; + default:return false; + } + } + return true; +} + +/** + * Remove the whitespace between two start/stop nodes with differing names. + */ +std::deque > RemoveNonSemanticWhiteSpace( + const std::deque >& nodes +) { + std::deque > result; + + if (nodes.size() <= 2) { + for (const auto& node : nodes + ) { + result.emplace_back(node->Copy()); + } + return result; + } + + INode* previous = nullptr; + StartNode* previous_as_start = nullptr; + + auto it = nodes.begin(); + INode* current = it->get(); + StartNode* current_as_start = dynamic_cast(current); + TextNode* current_as_text = dynamic_cast(current); + + ++it; + INode* lookahead = it->get(); + StartNode* lookahead_as_start = dynamic_cast(lookahead); + StopNode* lookahead_as_stop = dynamic_cast(lookahead); + TextNode* lookahead_as_text = dynamic_cast(lookahead); + + ++it; + + while (true) { + // region Determine what to do + + // If not set, we include the current node in the result. + bool ignore = ( + current_as_text != nullptr + && IsWhitespace(current_as_text->text) + && ( + previous == nullptr + || lookahead == nullptr + || !( + previous_as_start != nullptr + && lookahead_as_stop != nullptr + && previous_as_start->name == lookahead_as_stop->name + ) + ) + ); + // endregion + + // region Act + if (!ignore) { + result.emplace_back(current->Copy()); + } + // endregion + + // region Shift + previous = current; + previous_as_start = current_as_start; + + current = lookahead; + current_as_start = lookahead_as_start; + current_as_text = lookahead_as_text; + + if (current == nullptr) { + break; + } + + if (it == nodes.end()) { + lookahead = nullptr; + lookahead_as_start = nullptr; + lookahead_as_stop = nullptr; + lookahead_as_text = nullptr; + } else { + lookahead = it->get(); + lookahead_as_start = dynamic_cast(lookahead); + lookahead_as_stop = dynamic_cast(lookahead); + lookahead_as_text = dynamic_cast(lookahead); + + ++it; + } + // endregion + } + + return result; +} + +} // namespace canonicalizer + +std::string CanonicalizeXml( + const std::string& xml +) { + const char namespace_separator = ':'; + XML_Parser parser = XML_ParserCreateNS(nullptr, namespace_separator); + + std::unique_ptr our_data( + our::common::make_unique( + parser + ) + ); + + XML_SetUserData(parser, our_data.get()); + XML_SetElementHandler( + parser, + canonicalizer::OnStartElement, + canonicalizer::OnStopElement + ); + XML_SetCharacterDataHandler(parser, canonicalizer::OnText); + + XML_Status status = XML_Parse( + parser, + xml.data(), + static_cast(xml.size()), + true + ); + + if (status == XML_STATUS_ERROR) { + XML_Error error_code = XML_GetErrorCode(parser); + + std::string message; + + if (error_code == XML_ERROR_ABORTED) { + if (!our_data->error.has_value()) { + throw std::logic_error( + "The XML parsing was aborted, " + "but the error in our_data was empty" + ); + } + + message = std::move(*our_data->error); + } else { + const XML_LChar* error_str = XML_ErrorString(error_code); + message = error_str; + } + + throw std::invalid_argument( + our::common::Concat( + "Failed to parse XML for canonicalization: ", + std::to_string(XML_GetCurrentLineNumber(parser)), + ":", + std::to_string(XML_GetCurrentColumnNumber(parser)), + ": ", + message + ) + ); + } + + std::deque< + std::unique_ptr + > nodes( + std::move(our_data->nodes) + ); + + nodes = canonicalizer::MergeTextNodes(nodes); + + nodes = canonicalizer::RemoveNonSemanticWhiteSpace(nodes); + + size_t size = 0; + for (const auto& node : nodes) { + size += node->text.size(); + } + + std::string result; + result.reserve(size); + + for (const auto& node : nodes) { + result.append(node->text); + } + + return result; +} + +std::shared_ptr< + dummy::types::IClass +> MustDeserializeFile( + const std::filesystem::path& path +) { + std::ifstream ifs(path, std::ios::binary); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From( + ifs + ); + + if (ifs.bad()) { + throw std::runtime_error( + our::common::Concat( + "The file stream is in the bad mode after " + "reading and parsing the file as XML: ", + path.string() + ) + ); + } + + if (!deserialized.has_value()) { + throw std::runtime_error( + our::common::Concat( + "Failed to de-serialize from ", + path.string(), + ": ", + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ) + ); + } + + return std::move(deserialized.value()); +} + +} // namespace xmlization +} // namespace common +} // namespace test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.hpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.hpp new file mode 100644 index 000000000..d2fd9bbb2 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/common_xmlization.hpp @@ -0,0 +1,50 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Provide methods which are used throughout the jsonization tests. + */ +#ifndef TEST_COMMON_XMLIZATION_GUARD_ +#define TEST_COMMON_XMLIZATION_GUARD_ + +#include + +#include +#include +#include + +namespace test { +namespace common { +namespace xmlization { + +/** + * Read the content of the `path` and parse it as XML representation of an instance. + */ +std::shared_ptr< + dummy::types::IClass +> MustReadInstance( + const std::filesystem::path& path +); + +/** + * Canonicalize the `xml` by stripping away any XML text between the stop and start + * nodes. + */ +std::string CanonicalizeXml( + const std::string& xml +); + +std::shared_ptr< + dummy::types::IClass +> MustDeserializeFile( + const std::filesystem::path& path +); + +} // namespace xmlization +} // namespace common +} // namespace test + +#endif // TEST_COMMON_XMLIZATION_GUARD_ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_descent_and_descent_once.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_descent_and_descent_once.cpp new file mode 100644 index 000000000..516e29cd1 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_descent_and_descent_once.cpp @@ -0,0 +1,264 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_examples.hpp" +#include "./common_xmlization.hpp" + +#include +#include +#include + +#include + +#include + +namespace our = dummy; + +const std::filesystem::path& DetermineXmlDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "Xml"; + } + + return *result; +} + +const std::filesystem::path& DetermineDescentDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "Descent"; + } + + return *result; +} + +const std::filesystem::path& DetermineDescentOnceDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "DescentOnce"; + } + + return *result; +} + +void AssertOrRerecordDescent( + const std::filesystem::path& xml_path, + const std::filesystem::path& trace_path +) { + std::shared_ptr< + our::types::IClass + > instance( + test::common::xmlization::MustDeserializeFile(xml_path) + ); + + std::deque parts; + + for ( + const std::shared_ptr& something + : our::iteration::Descent(instance) + ) { + parts.emplace_back(test::common::TraceMark(*something)); + parts.push_back(L"\n"); + } + + test::common::AssertContentEqualsExpectedOrRecord( + our::common::WstringToUtf8( + test::common::JoinWstrings(parts, L"") + ), + trace_path + ); +} + +void AssertOrRerecordDescentOnce( + const std::shared_ptr& instance, + const std::filesystem::path& trace_path +) { + std::deque parts; + + for ( + const std::shared_ptr& something + : our::iteration::DescentOnce(instance) + ) { + parts.emplace_back(test::common::TraceMark(*something)); + parts.push_back(L"\n"); + } + + test::common::AssertContentEqualsExpectedOrRecord( + our::common::WstringToUtf8( + test::common::JoinWstrings(parts, L"") + ), + trace_path + ); +} + +TEST_CASE("Test Descent over an SomeItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "someItem", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + const std::filesystem::path parent( + ( + DetermineDescentDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path trace_path( + parent + / (path.filename().string() + ".trace") + ); + + AssertOrRerecordDescent(path, trace_path); + } +} + +TEST_CASE("Test DescentOnce over an SomeItem") { + const std::shared_ptr< + our::types::ISomeItem + > instance( + test::common::examples::LoadMaxSomeItem() + ); + + const std::filesystem::path trace_path( + DetermineDescentOnceDir() + / "MaxSomeItem.trace" + ); + + AssertOrRerecordDescentOnce(instance, trace_path); +} + +TEST_CASE("Test Descent over an SpecialItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "specialItem", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + const std::filesystem::path parent( + ( + DetermineDescentDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path trace_path( + parent + / (path.filename().string() + ".trace") + ); + + AssertOrRerecordDescent(path, trace_path); + } +} + +TEST_CASE("Test DescentOnce over an SpecialItem") { + const std::shared_ptr< + our::types::ISpecialItem + > instance( + test::common::examples::LoadMaxSpecialItem() + ); + + const std::filesystem::path trace_path( + DetermineDescentOnceDir() + / "MaxSpecialItem.trace" + ); + + AssertOrRerecordDescentOnce(instance, trace_path); +} + +TEST_CASE("Test Descent over an Container") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "container", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + const std::filesystem::path parent( + ( + DetermineDescentDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path trace_path( + parent + / (path.filename().string() + ".trace") + ); + + AssertOrRerecordDescent(path, trace_path); + } +} + +TEST_CASE("Test DescentOnce over an Container") { + const std::shared_ptr< + our::types::IContainer + > instance( + test::common::examples::LoadMaxContainer() + ); + + const std::filesystem::path trace_path( + DetermineDescentOnceDir() + / "MaxContainer.trace" + ); + + AssertOrRerecordDescentOnce(instance, trace_path); +} + +TEST_CASE("Test Descent over an Wrapper") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "wrapper", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + const std::filesystem::path parent( + ( + DetermineDescentDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path trace_path( + parent + / (path.filename().string() + ".trace") + ); + + AssertOrRerecordDescent(path, trace_path); + } +} + +TEST_CASE("Test DescentOnce over an Wrapper") { + const std::shared_ptr< + our::types::IWrapper + > instance( + test::common::examples::LoadMaxWrapper() + ); + + const std::filesystem::path trace_path( + DetermineDescentOnceDir() + / "MaxWrapper.trace" + ); + + AssertOrRerecordDescentOnce(instance, trace_path); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_dispatch.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_dispatch.cpp new file mode 100644 index 000000000..0701b94e2 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_dispatch.cpp @@ -0,0 +1,125 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_jsonization.hpp" +#include "./common_examples.hpp" + +#include + +#include + +namespace our = dummy; + +TEST_CASE("Test the round-trip of an expected IItem") { + const std::shared_ptr< + our::types::ISomeItem + > concrete_instance( + test::common::examples::LoadMinSomeItem() + ); + + const nlohmann::json json = our::jsonization::Serialize( + *concrete_instance + ); + + our::common::expected< + std::shared_ptr< + our::types::IItem + >, + our::jsonization::DeserializationError + > instance = our::jsonization::ItemFrom( + json + ); + + if (!instance.has_value()) { + INFO( + our::common::Concat( + "Failed to deserialize a IItem " + "from a minimal ISomeItem: ", + our::common::WstringToUtf8(instance.error().path.ToWstring()), + ": ", + our::common::WstringToUtf8(instance.error().cause) + ) + ) + CHECK(instance.has_value()); + } + + const nlohmann::json another_json = our::jsonization::Serialize( + **instance + ); + + const std::optional< + std::string + > patch_message = test::common::jsonization::CompareJsons( + json, + another_json + ); + if (patch_message.has_value()) { + INFO( + our::common::Concat( + "Failed to make a round-trip of a IItem " + "over a minimal ISomeItem: ", + *patch_message + ) + ) + CHECK(!patch_message.has_value()); + } +} + +TEST_CASE("Test the round-trip of an expected ISomeItem") { + const std::shared_ptr< + our::types::ISpecialItem + > concrete_instance( + test::common::examples::LoadMinSpecialItem() + ); + + const nlohmann::json json = our::jsonization::Serialize( + *concrete_instance + ); + + our::common::expected< + std::shared_ptr< + our::types::ISomeItem + >, + our::jsonization::DeserializationError + > instance = our::jsonization::SomeItemFrom( + json + ); + + if (!instance.has_value()) { + INFO( + our::common::Concat( + "Failed to deserialize a ISomeItem " + "from a minimal ISpecialItem: ", + our::common::WstringToUtf8(instance.error().path.ToWstring()), + ": ", + our::common::WstringToUtf8(instance.error().cause) + ) + ) + CHECK(instance.has_value()); + } + + const nlohmann::json another_json = our::jsonization::Serialize( + **instance + ); + + const std::optional< + std::string + > patch_message = test::common::jsonization::CompareJsons( + json, + another_json + ); + if (patch_message.has_value()) { + INFO( + our::common::Concat( + "Failed to make a round-trip of a ISomeItem " + "over a minimal ISpecialItem: ", + *patch_message + ) + ) + CHECK(!patch_message.has_value()); + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_of_concrete_classes.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_of_concrete_classes.cpp new file mode 100644 index 000000000..987c8d300 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_jsonization_of_concrete_classes.cpp @@ -0,0 +1,467 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_jsonization.hpp" + +#include + +#include + +#include + +namespace our = dummy; + +template +void AssertRoundTrip( + const std::filesystem::path& path, + std::function< + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + >(const nlohmann::json&, bool) + > deserialization_function +) { + const nlohmann::json json = test::common::jsonization::MustReadJson(path); + + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + > deserialized = deserialization_function(json, false); + + if (!deserialized.has_value()) { + INFO( + our::common::Concat( + "Failed to de-serialize from ", + path.string(), + ": ", + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ) + ) + REQUIRE(deserialized.has_value()); + } + + nlohmann::json another_json = our::jsonization::Serialize( + *(deserialized.value()) + ); + + std::optional diff_message = test::common::jsonization::CompareJsons( + json, + another_json + ); + if (diff_message.has_value()) { + INFO( + our::common::Concat( + "The JSON round-trip from ", + path.string(), + " failed. There is a diff between the original JSON " + "and the serialized one: ", + *diff_message + ) + ) + REQUIRE(!diff_message.has_value()); + } +} + +template +void AssertDeserializationFailure( + const std::filesystem::path& path, + std::function< + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + >(const nlohmann::json&, bool) + > deserialization_function, + const std::filesystem::path& error_path +) { + const nlohmann::json json = test::common::jsonization::MustReadJson(path); + + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + > deserialized = deserialization_function(json, false); + + if (deserialized.has_value()) { + INFO( + our::common::Concat( + "Expected the de-serialization to fail on ", + path.string(), + ", but the de-serialization succeeded" + ) + ) + REQUIRE(!deserialized.has_value()); + } + + test::common::AssertContentEqualsExpectedOrRecord( + our::common::Concat( + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ), + error_path + ); +} + +const std::filesystem::path& DetermineJsonDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "Json"; + } + + return *result; +} + +const std::filesystem::path& DetermineErrorDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "JsonizationError"; + } + + return *result; +} + +template +std::shared_ptr MustDeserializeTheFirstExpected( + const std::string& model_type, + std::function< + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + >(const nlohmann::json&, bool) + > deserialization_function +) { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineJsonDir() / "Expected" / model_type, + ".json" + ) + ); + + INFO("We expect at least one recorded example of " + model_type) + REQUIRE(!paths.empty()); + + const nlohmann::json json = test::common::jsonization::MustReadJson( + paths.front() + ); + + our::common::expected< + std::shared_ptr, + our::jsonization::DeserializationError + > deserialized = deserialization_function(json, false); + + INFO( + our::common::Concat( + "Failed to de-serialize from ", + paths.front().string() + ) + ) + REQUIRE(deserialized.has_value()); + + return deserialized.value(); +} + +/** + * Assert that \p that instance can not be serialized to JSON, and that + * the failure is reported at \p expected_path. + */ +void AssertSerializationFailsAt( + const our::types::IClass& that, + const std::string& expected_path +) { + try { + our::jsonization::Serialize(that); + } catch (const our::jsonization::SerializationException& exception) { + const std::string observed_path( + our::common::WstringToUtf8( + exception.path().ToWstring() + ) + ); + + INFO( + our::common::Concat( + "Expected the serialization to fail at ", + expected_path, + ", but it failed at ", + observed_path, + ": ", + our::common::WstringToUtf8(exception.cause()) + ) + ) + REQUIRE(observed_path == expected_path); + + return; + } + + INFO( + our::common::Concat( + "Expected the serialization to fail at ", + expected_path, + ", but it succeeded" + ) + ) + REQUIRE(false); +} + +TEST_CASE("Test the round-trip of an expected SomeItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineJsonDir() + / "Expected" + / "SomeItem", + ".json" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertRoundTrip< + our::types::ISomeItem + >(path, our::jsonization::SomeItemFrom); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected SomeItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineJsonDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "SomeItem", + ".json" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineJsonDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure< + our::types::ISomeItem + >( + path, + our::jsonization::SomeItemFrom, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected SpecialItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineJsonDir() + / "Expected" + / "SpecialItem", + ".json" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertRoundTrip< + our::types::ISpecialItem + >(path, our::jsonization::SpecialItemFrom); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected SpecialItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineJsonDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "SpecialItem", + ".json" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineJsonDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure< + our::types::ISpecialItem + >( + path, + our::jsonization::SpecialItemFrom, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected Container") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineJsonDir() + / "Expected" + / "Container", + ".json" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertRoundTrip< + our::types::IContainer + >(path, our::jsonization::ContainerFrom); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected Container") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineJsonDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "Container", + ".json" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineJsonDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure< + our::types::IContainer + >( + path, + our::jsonization::ContainerFrom, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected Wrapper") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineJsonDir() + / "Expected" + / "Wrapper", + ".json" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertRoundTrip< + our::types::IWrapper + >(path, our::jsonization::WrapperFrom); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected Wrapper") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineJsonDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "Wrapper", + ".json" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineJsonDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure< + our::types::IWrapper + >( + path, + our::jsonization::WrapperFrom, + error_path + ); + } + } +} + +TEST_CASE( + "Test the serialization failure on an integer outside the range representable in JSON " + "at .level of SpecialItem" +) { + for ( + const int64_t value + : { + 9007199254740992LL, + -9007199254740992LL + } + ) { + std::shared_ptr instance( + MustDeserializeTheFirstExpected( + "SpecialItem", + our::jsonization::SpecialItemFrom + ) + ); + + instance->set_level(value); + + AssertSerializationFailsAt( + *instance, + ".level" + ); + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_revm.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_revm.cpp new file mode 100644 index 000000000..737925e52 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_revm.cpp @@ -0,0 +1,286 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Test the virtual machine for matching regular expressions. + */ + +#include "dummy/common.hpp" +#include "dummy/revm.hpp" + +#include + +namespace our = dummy; + +TEST_CASE("Test character in an empty range") { + std::vector ranges; + + REQUIRE( + !our::revm::CharacterInRanges(ranges, L'M') + ); +} + +TEST_CASE("Test character in a single range") { + std::vector ranges = { + our::revm::Range(L'A', L'Z') + }; + + REQUIRE(CharacterInRanges(ranges, L'M')); + REQUIRE(!CharacterInRanges(ranges, L'a')); +} + +TEST_CASE("Test character in multiple ranges") { + std::vector ranges = { + our::revm::Range(L'A', L'C'), + our::revm::Range(L'D', L'F'), + our::revm::Range(L'G', L'I'), + our::revm::Range(L'J', L'L'), + our::revm::Range(L'M', L'N') + }; + + for (wchar_t character = L'A'; character < L'Z'; ++character) { + const std::string message( + our::common::WstringToUtf8( + L"Testing for character: " + std::wstring(1, character) + ) + ); + + if (character <= L'N') { + INFO(message) + REQUIRE( + CharacterInRanges(ranges, character) + ); + } else { + INFO(message) + REQUIRE( + !CharacterInRanges(ranges, character) + ); + } + } +} + +TEST_CASE("Test pattern ^a$") { + std::vector > program; + + program.emplace_back( + std::make_unique(L'a') + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^a+b+$") { + std::vector > program; + + program.emplace_back( + std::make_unique(L'a') + ); + program.emplace_back( + std::make_unique(0, 2) + ); + program.emplace_back( + std::make_unique(L'b') + ); + program.emplace_back( + std::make_unique(2, 4) + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(!our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(our::revm::Match(program, L"ab")); + REQUIRE(our::revm::Match(program, L"aabb")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^a|b$") { + std::vector > program; + + program.emplace_back( + std::make_unique(1, 3) + ); + program.emplace_back( + std::make_unique(L'a') + ); + program.emplace_back( + std::make_unique(4) + ); + program.emplace_back( + std::make_unique(L'b') + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^a?$") { + std::vector > program; + + program.emplace_back( + std::make_unique(1, 2) + ); + program.emplace_back( + std::make_unique(L'a') + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^a*$") { + std::vector > program; + + program.emplace_back( + std::make_unique(1, 3) + ); + program.emplace_back( + std::make_unique(L'a') + ); + program.emplace_back( + std::make_unique(0) + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^[a-b]$") { + std::vector > program; + + program.emplace_back( + std::make_unique( + std::vector{ + our::revm::Range(L'a', L'b') + } + ) + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern ^[^a-b]$") { + std::vector > program; + + program.emplace_back( + std::make_unique( + std::vector{ + our::revm::Range(L'a', L'b') + } + ) + ); + program.emplace_back( + std::make_unique() + ); + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(!our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); + REQUIRE(our::revm::Match(program, L"c")); + REQUIRE(!our::revm::Match(program, L"cc")); +} + +TEST_CASE("Test pattern ^.$") { + std::vector > program; + + program.emplace_back(std::make_unique()); + program.emplace_back(std::make_unique()); + program.emplace_back(std::make_unique()); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(!our::revm::Match(program, L"aa")); + REQUIRE(our::revm::Match(program, L"b")); + REQUIRE(!our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +TEST_CASE("Test pattern with arbitrary suffix ^a.*$") { + std::vector > program; + + program.emplace_back( + std::make_unique(L'a') + ); + + // NOTE (mristin): + // Usually, the last two instructions would be `end` followed by a `match`. + // However, we can optimize by having a single `match` without an `end` so that + // we return early. + program.emplace_back( + std::make_unique() + ); + + REQUIRE(!our::revm::Match(program, L"")); + REQUIRE(our::revm::Match(program, L"a")); + REQUIRE(our::revm::Match(program, L"aa")); + REQUIRE(!our::revm::Match(program, L"b")); + REQUIRE(our::revm::Match(program, L"ab")); + REQUIRE(!our::revm::Match(program, L"ba")); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_string_helpers.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_string_helpers.cpp new file mode 100644 index 000000000..0a0f66244 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_string_helpers.cpp @@ -0,0 +1,349 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Test `len`, the slicing of strings and `find` as used in the transpiled code. + * + * The transpiled code follows the Python implementation, since Python is + * the language of the meta-model specifications. Hence, the lengths and + * the positions count the characters (code points). The expected values have + * been computed with Python, and all the SDKs test against the very same cases. + */ + +#include "dummy/common.hpp" + +#include + +namespace our = dummy; + +TEST_CASE("len('') gives 0: empty text") { + REQUIRE( + our::common::LenStr(L"") + == 0U + ); +} + +TEST_CASE("len('abc') gives 3: ASCII text") { + REQUIRE( + our::common::LenStr(L"abc") + == 3U + ); +} + +TEST_CASE("len('\303\251-') gives 2: character of two UTF-8 bytes") { + REQUIRE( + our::common::LenStr(L"\u00e9-") + == 2U + ); +} + +TEST_CASE("len('\360\237\230\200-') gives 2: character beyond the Basic Multilingual Plane") { + REQUIRE( + our::common::LenStr(L"\U0001f600-") + == 2U + ); +} + +TEST_CASE("len('a\303\251\360\237\230\200b\360\237\230\200') gives 5: mixed characters") { + REQUIRE( + our::common::LenStr(L"a\u00e9\U0001f600b\U0001f600") + == 5U + ); +} + +TEST_CASE("'abcde'[1:3] gives 'bc': start and end") { + REQUIRE( + our::common::SliceStr(L"abcde", 1, 3) + == L"bc" + ); +} + +TEST_CASE("'abcde'[:2] gives 'ab': no start") { + REQUIRE( + our::common::SliceStr(L"abcde", 0, 2) + == L"ab" + ); +} + +TEST_CASE("'abcde'[3:] gives 'de': no end") { + REQUIRE( + our::common::SliceStr(L"abcde", 3) + == L"de" + ); +} + +TEST_CASE("'abcde'[:] gives 'abcde': neither start nor end") { + REQUIRE( + our::common::SliceStr(L"abcde", 0) + == L"abcde" + ); +} + +TEST_CASE("'abcde'[-2:] gives 'de': negative start counts from the end") { + REQUIRE( + our::common::SliceStr(L"abcde", -2) + == L"de" + ); +} + +TEST_CASE("'abcde'[:-2] gives 'abc': negative end counts from the end") { + REQUIRE( + our::common::SliceStr(L"abcde", 0, -2) + == L"abc" + ); +} + +TEST_CASE("'abcde'[-4:-1] gives 'bcd': negative start and end") { + REQUIRE( + our::common::SliceStr(L"abcde", -4, -1) + == L"bcd" + ); +} + +TEST_CASE("'abcde'[-10:2] gives 'ab': negative start before the beginning is clamped") { + REQUIRE( + our::common::SliceStr(L"abcde", -10, 2) + == L"ab" + ); +} + +TEST_CASE("'abcde'[2:10] gives 'cde': end beyond the end is clamped") { + REQUIRE( + our::common::SliceStr(L"abcde", 2, 10) + == L"cde" + ); +} + +TEST_CASE("'abcde'[10:] gives '': start beyond the end gives empty") { + REQUIRE( + our::common::SliceStr(L"abcde", 10) + == L"" + ); +} + +TEST_CASE("'abcde'[3:1] gives '': start after end gives empty") { + REQUIRE( + our::common::SliceStr(L"abcde", 3, 1) + == L"" + ); +} + +TEST_CASE("'abcde'[-1:-3] gives '': negative start after negative end gives empty") { + REQUIRE( + our::common::SliceStr(L"abcde", -1, -3) + == L"" + ); +} + +TEST_CASE("'abcde'[5:5] gives '': start equal to end gives empty") { + REQUIRE( + our::common::SliceStr(L"abcde", 5, 5) + == L"" + ); +} + +TEST_CASE("''[0:0] gives '': empty text") { + REQUIRE( + our::common::SliceStr(L"", 0, 0) + == L"" + ); +} + +TEST_CASE("''[-1:] gives '': negative start on empty text") { + REQUIRE( + our::common::SliceStr(L"", -1) + == L"" + ); +} + +TEST_CASE("''[:5] gives '': end beyond the end of empty text") { + REQUIRE( + our::common::SliceStr(L"", 0, 5) + == L"" + ); +} + +TEST_CASE("'\303\251-x'[1:] gives '-x': after a character of two UTF-8 bytes") { + REQUIRE( + our::common::SliceStr(L"\u00e9-x", 1) + == L"-x" + ); +} + +TEST_CASE("'a\303\251b'[1:2] gives '\303\251': character of two UTF-8 bytes") { + REQUIRE( + our::common::SliceStr(L"a\u00e9b", 1, 2) + == L"\u00e9" + ); +} + +TEST_CASE("'a\360\237\230\200b'[1:2] gives '\360\237\230\200': character beyond the Basic Multilingual Plane") { + REQUIRE( + our::common::SliceStr(L"a\U0001f600b", 1, 2) + == L"\U0001f600" + ); +} + +TEST_CASE("'\360\237\230\200-x'[1:] gives '-x': after a character beyond the Basic Multilingual Plane") { + REQUIRE( + our::common::SliceStr(L"\U0001f600-x", 1) + == L"-x" + ); +} + +TEST_CASE("'a\303\251\360\237\230\200b\360\237\230\200'[-3:-1] gives '\360\237\230\200b': negative start on characters beyond ASCII") { + REQUIRE( + our::common::SliceStr(L"a\u00e9\U0001f600b\U0001f600", -3, -1) + == L"\U0001f600b" + ); +} + +TEST_CASE("'abcabc'.find('b') gives 1: found") { + REQUIRE( + our::common::FindStr(L"abcabc", L"b") + == 1 + ); +} + +TEST_CASE("'abcabc'.find('x') gives -1: not found") { + REQUIRE( + our::common::FindStr(L"abcabc", L"x") + == -1 + ); +} + +TEST_CASE("'abcabc'.find('') gives 0: empty sub") { + REQUIRE( + our::common::FindStr(L"abcabc", L"") + == 0 + ); +} + +TEST_CASE("'ab'.find('abc') gives -1: sub longer than text") { + REQUIRE( + our::common::FindStr(L"ab", L"abc") + == -1 + ); +} + +TEST_CASE("'abcabc'.find('b', 2) gives 4: found after start") { + REQUIRE( + our::common::FindStr(L"abcabc", L"b", 2) + == 4 + ); +} + +TEST_CASE("'abcabc'.find('b', 5) gives -1: found only before start") { + REQUIRE( + our::common::FindStr(L"abcabc", L"b", 5) + == -1 + ); +} + +TEST_CASE("'abcabc'.find('c', -1) gives 5: negative start counts from the end") { + REQUIRE( + our::common::FindStr(L"abcabc", L"c", -1) + == 5 + ); +} + +TEST_CASE("'abcabc'.find('a', -3) gives 3: negative start finds the later occurrence") { + REQUIRE( + our::common::FindStr(L"abcabc", L"a", -3) + == 3 + ); +} + +TEST_CASE("'abcabc'.find('a', -10) gives 0: negative start before the beginning is clamped") { + REQUIRE( + our::common::FindStr(L"abcabc", L"a", -10) + == 0 + ); +} + +TEST_CASE("'abcabc'.find('', 6) gives 6: empty sub at the end") { + REQUIRE( + our::common::FindStr(L"abcabc", L"", 6) + == 6 + ); +} + +TEST_CASE("'abcabc'.find('', 7) gives -1: empty sub beyond the end gives -1") { + REQUIRE( + our::common::FindStr(L"abcabc", L"", 7) + == -1 + ); +} + +TEST_CASE("'abcabc'.find('c', 10) gives -1: start beyond the end gives -1") { + REQUIRE( + our::common::FindStr(L"abcabc", L"c", 10) + == -1 + ); +} + +TEST_CASE("''.find('') gives 0: empty sub in empty text") { + REQUIRE( + our::common::FindStr(L"", L"") + == 0 + ); +} + +TEST_CASE("''.find('', 1) gives -1: empty sub beyond the end of empty text gives -1") { + REQUIRE( + our::common::FindStr(L"", L"", 1) + == -1 + ); +} + +TEST_CASE("''.find('x') gives -1: not found in empty text") { + REQUIRE( + our::common::FindStr(L"", L"x") + == -1 + ); +} + +TEST_CASE("'\303\251-'.find('-') gives 1: after a character of two UTF-8 bytes") { + REQUIRE( + our::common::FindStr(L"\u00e9-", L"-") + == 1 + ); +} + +TEST_CASE("'\360\237\230\200-'.find('-') gives 1: after a character beyond the Basic Multilingual Plane") { + REQUIRE( + our::common::FindStr(L"\U0001f600-", L"-") + == 1 + ); +} + +TEST_CASE("'a\360\237\230\200b'.find('\360\237\230\200') gives 1: character beyond the Basic Multilingual Plane") { + REQUIRE( + our::common::FindStr(L"a\U0001f600b", L"\U0001f600") + == 1 + ); +} + +TEST_CASE("'\360\237\230\200a\360\237\230\200a'.find('a', 2) gives 3: start after a character beyond ASCII") { + REQUIRE( + our::common::FindStr(L"\U0001f600a\U0001f600a", L"a", 2) + == 3 + ); +} + +TEST_CASE("'\360\237\230\200a\360\237\230\200a'.find('\360\237\230\200', -2) gives 2: negative start on characters beyond ASCII") { + REQUIRE( + our::common::FindStr(L"\U0001f600a\U0001f600a", L"\U0001f600", -2) + == 2 + ); +} + +TEST_CASE("'\303\251\360\237\230\200'.find('', 3) gives -1: start beyond the end of characters beyond ASCII") { + REQUIRE( + our::common::FindStr(L"\u00e9\U0001f600", L"", 3) + == -1 + ); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_base64.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_base64.cpp new file mode 100644 index 000000000..6e9de0510 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_base64.cpp @@ -0,0 +1,337 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Test base64 encoding and decoding. + */ + +#include "dummy/stringification.hpp" + +#include + +namespace our = dummy; + +std::string BytesToString(const std::vector& bytes) { + std::string result; + result.resize(bytes.size()); + for (size_t i = 0; i < bytes.size(); ++i + ) { + result[i] = bytes[i]; + } + return result; +} + +std::vector StringToBytes(const std::string& text) { + std::vector result; + result.resize(text.size()); + for (size_t i = 0; i < text.size(); ++i + ) { + result[i] = text[i]; + } + return result; +} + +void AssertEncodeDecode( + const std::string& text, + const std::string& expected_encoded +) { + const std::string encoded = our::stringification::Base64Encode( + StringToBytes(text) + ); + + REQUIRE( + expected_encoded + == encoded + ); + + our::common::expected< + std::vector, + std::string + > bytes = our::stringification::Base64Decode(encoded); + + REQUIRE(bytes.has_value()); + + REQUIRE( + BytesToString(bytes.value()) + == text + ); +} + +TEST_CASE("Test '' is encoded ''") { + AssertEncodeDecode( + "", + "" + ); +} + +TEST_CASE("Test 'f' is encoded 'Zg=='") { + AssertEncodeDecode( + "f", + "Zg==" + ); +} + +TEST_CASE("Test 'fo' is encoded 'Zm8='") { + AssertEncodeDecode( + "fo", + "Zm8=" + ); +} + +TEST_CASE("Test 'foo' is encoded 'Zm9v'") { + AssertEncodeDecode( + "foo", + "Zm9v" + ); +} + +TEST_CASE("Test 'foob' is encoded 'Zm9vYg=='") { + AssertEncodeDecode( + "foob", + "Zm9vYg==" + ); +} + +TEST_CASE("Test 'fooba' is encoded 'Zm9vYmE='") { + AssertEncodeDecode( + "fooba", + "Zm9vYmE=" + ); +} + +TEST_CASE("Test 'foobar' is encoded 'Zm9vYmFy'") { + AssertEncodeDecode( + "foobar", + "Zm9vYmFy" + ); +} + +TEST_CASE("Test unexpected padding in the middle") { + const std::string encoded = "Zm9vYmFy"; + for (size_t i = 0; i < encoded.size() - 1; ++i + ) { + const std::string bad_encoded( + encoded.substr(0, i) + + "=" + + encoded.substr(i + 1, encoded.size() - i) + ); + + our::common::expected< + std::vector, + std::string + > bytes = our::stringification::Base64Decode(bad_encoded); + + REQUIRE(!bytes.has_value()); + + std::stringstream ss; + ss + << "Expected a valid character from base64-encoded string, " + << "but got at index " << i << ": 61 (code: 61)"; + + REQUIRE( + bytes.error() + == ss.str() + ); + } +} + +// NOTE (mristin): +// The following checks come from: https://eprint.iacr.org/2022/361.pdf +TEST_CASE("Test 'Hello' is encoded as 'SGVsbG8='") { + AssertEncodeDecode( + "Hello", + "SGVsbG8=" + ); +} + +// NOTE (mristin): +// This is not a test case, but we merely document that there is a possible +// attack vector where different strings encode the *same* byte sequence. +TEST_CASE("Test that our implementation suffers from padding inconsistency of 'Hello' as 'SGVsbG9='") { + const std::string encoded = "SGVsbG9="; + + our::common::expected< + std::vector, + std::string + > bytes = our::stringification::Base64Decode(encoded); + + REQUIRE(bytes.has_value()); + + REQUIRE( + BytesToString(bytes.value()) + == "Hello" + ); +} + +void AssertDecode( + const std::string& encoded, + const std::vector& expected_decoded +) { + our::common::expected< + std::vector, + std::string + > bytes = our::stringification::Base64Decode(encoded); + + INFO(encoded) + REQUIRE(bytes.has_value()); + + INFO(encoded) + REQUIRE(bytes->size() == expected_decoded.size()); + + for (size_t i = 0; i < bytes->size(); ++i + ) { + INFO(encoded + " is not correctly decoded at " + std::to_string(i)) + REQUIRE(bytes->at(i) == expected_decoded[i]); + } +} + +// NOTE (mristin): +// The following tests have been scraped from: +// https://github.com/rickkas7/Base64RK/blob/183d20e62b96ef9b0230c80c7a172715ca6f661e/test/unit-test/unit-test.cpp +TEST_CASE("Test table from rickkas7/Base64RK") { + AssertDecode( + "", + {} + ); + + AssertDecode( + "HA==", + {28} + ); + + AssertDecode( + "rtA=", + {174, 208} + ); + + AssertDecode( + "MuDQ", + {50, 224, 208} + ); + + AssertDecode( + "LRLrBg==", + {45, 18, 235, 6} + ); + + AssertDecode( + "dceZ3aA=", + {117, 199, 153, 221, 160} + ); + + AssertDecode( + "jQy8MK0t", + {141, 12, 188, 48, 173, 45} + ); + + AssertDecode( + "RY+UfdUDlA==", + {69, 143, 148, 125, 213, 3, 148} + ); + + AssertDecode( + "+6KmPIGDLg0=", + {251, 162, 166, 60, 129, 131, 46, 13} + ); + + AssertDecode( + "zyjM/k1HPQiA", + {207, 40, 204, 254, 77, 71, 61, 8, 128} + ); + + AssertDecode( + "nJbVrnU7RtzSPw==", + {156, 150, 213, 174, 117, 59, 70, 220, 210, 63} + ); + + AssertDecode( + "wDatE35UGvqnBTI=", + {192, 54, 173, 19, 126, 84, 26, 250, 167, 5, 50} + ); + + AssertDecode( + "FAEPTeCNmS+6qsjj", + {20, 1, 15, 77, 224, 141, 153, 47, 186, 170, 200, 227} + ); + + AssertDecode( + "VaaCqcF1wlHuQLTkfw==", + {85, 166, 130, 169, 193, 117, 194, 81, 238, 64, 180, 228, 127} + ); + + AssertDecode( + "QYKREfdjniR1CMABweo=", + {65, 130, 145, 17, 247, 99, 158, 36, 117, 8, 192, 1, 193, 234} + ); + + AssertDecode( + "CTf9s8td7Tr8yKiRczUf", + {9, 55, 253, 179, 203, 93, 237, 58, 252, 200, 168, 145, 115, 53, 31} + ); + + AssertDecode( + "/kyMTRA0d+W64W21hiLlmesBEH4RDg0MivjmPEmAdMzP", + {254, 76, 140, 77, 16, 52, 119, 229, 186, 225, 109, 181, 134, 34, + 229, 153, 235, 1, 16, 126, 17, 14, 13, 12, 138, + 248, 230, 60, 73, 128, 116, 204, 207} + ); + + AssertDecode( + "m5/QqN3YcQg6iE1dVjJQWw5mCjYCnsNLKPVUsXQ269l86M1xbyHn+/aIKafJ6hRT4UOD0mH" + "CAfye/VSHdwH+C7bQ", + {155, 159, 208, 168, 221, 216, 113, 8, 58, 136, 77, 93, 86, 50, 80, + 91, 14, 102, 10, 54, 2, 158, 195, 75, 40, 245, + 84, 177, 116, 54, 235, 217, 124, 232, 205, 113, 111, 33, 231, 251, 246, 136, 41, + 167, 201, 234, 20, 83, 225, 67, + 131, 210, 97, 194, 1, 252, 158, 253, 84, 135, 119, 1, 254, 11, 182, 208} + ); + + AssertDecode( + "ZSsh3WyA3d00CyimeMZPAI1/t+LF6DQdiAEQG9hHlfuDXhZ2Gf/Wd1u2y+RDFli0oSq2fbfv" + "utgXCxGQfMmxA1NEg/k0vQIcSqzWdX7571oWzKHz6KZnlhTwPF27QznnAWbdwkyiCFlIlpL3tYbyzbMT" + "m0ReUtY4AW9tTpvYSQdQ8w==", + {101, 43, 33, 221, 108, 128, 221, 221, 52, 11, 40, 166, 120, 198, + 79, 0, 141, 127, 183, 226, 197, 232, 52, 29, 136, + 1, 16, 27, 216, 71, 149, 251, 131, 94, 22, 118, 25, 255, 214, 119, 91, 182, 203, + 228, 67, 22, 88, 180, 161, 42, + 182, 125, 183, 239, 186, 216, 23, 11, 17, 144, 124, 201, 177, 3, 83, 68, 131, + 249, 52, 189, 2, 28, 74, 172, 214, + 117, 126, 249, 239, 90, 22, 204, 161, 243, 232, 166, 103, 150, 20, 240, 60, 93, + 187, 67, 57, 231, 1, 102, 221, 194, + 76, 162, 8, 89, 72, 150, 146, 247, 181, 134, 242, 205, 179, 19, 155, 68, 94, 82, + 214, 56, 1, 111, 109, 78, 155, + 216, 73, 7, 80, 243} + ); + + AssertDecode( + "seCV0jbCe42d4GMBvXIl5vqVoZ6K4gUSg6nKgi5tZiNxx22yYon5ReOC6PZpZWHqeJcfKVo" + "UD/Za1ieLVjROriNfP+Lxf6Tbroz/EXh2YYOFI+d110oObkYqbgcl6ovS4CRH40Sb7L89Uwu9WDzQA" + "215IEepOn1wdBCZyEryWY2PGdNcs+Pft9sSNtBQ/QtMRZYEMO5JpRmFyFDPVLZA+5xM/3Dj65odv1b" + "ZdtszJq0Gbw43ww0t7EK0J21uBy89Z2R1N1kjQGpqKu4lRhQIgCKDOHhnfZW2gtkmZhiqby9xT44Ep1" + "AMrmZKgW3MDPzIX2Ez16mUv3Gb5JHrI/w1sQ==", + {177, 224, 149, 210, 54, 194, 123, 141, 157, 224, 99, 1, 189, + 114, 37, 230, 250, 149, 161, 158, 138, 226, 5, 18, + 131, 169, 202, 130, 46, 109, 102, 35, 113, 199, 109, 178, 98, 137, 249, 69, 227, + 130, 232, 246, 105, 101, 97, 234, + 120, 151, 31, 41, 90, 20, 15, 246, 90, 214, 39, 139, 86, 52, 78, 174, 35, 95, 63, + 226, 241, 127, 164, 219, 174, + 140, 255, 17, 120, 118, 97, 131, 133, 35, 231, 117, 215, 74, 14, 110, 70, 42, + 110, 7, 37, 234, 139, 210, 224, 36, + 71, 227, 68, 155, 236, 191, 61, 83, 11, 189, 88, 60, 208, 3, 109, 121, 32, 71, + 169, 58, 125, 112, 116, 16, 153, + 200, 74, 242, 89, 141, 143, 25, 211, 92, 179, 227, 223, 183, 219, 18, 54, 208, + 80, 253, 11, 76, 69, 150, 4, 48, + 238, 73, 165, 25, 133, 200, 80, 207, 84, 182, 64, 251, 156, 76, 255, 112, 227, + 235, 154, 29, 191, 86, 217, 118, + 219, 51, 38, 173, 6, 111, 14, 55, 195, 13, 45, 236, 66, 180, 39, 109, 110, 7, + 47, 61, 103, 100, 117, 55, 89, 35, + 64, 106, 106, 42, 238, 37, 70, 20, 8, 128, 34, 131, 56, 120, 103, 125, 149, + 182, 130, 217, 38, 102, 24, 170, 111, + 47, 113, 79, 142, 4, 167, 80, 12, 174, 102, 74, 129, 109, 204, 12, 252, 200, + 95, 97, 51, 215, 169, 148, 191, 113, + 155, 228, 145, 235, 35, 252, 53, 177} + ); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_of_enums.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_of_enums.cpp new file mode 100644 index 000000000..18635acd3 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_stringification_of_enums.cpp @@ -0,0 +1,84 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/stringification.hpp" + +#include + +namespace our = dummy; + +TEST_CASE("Test ModelType round-trip") { + REQUIRE( + our::types::ModelType::kSomeItem + == our::stringification::MustModelTypeFromString( + "SomeItem" + ) + ); + + REQUIRE( + our::stringification::to_string( + our::types::ModelType::kSomeItem + ) + == "SomeItem" + ); + + REQUIRE( + our::types::ModelType::kSpecialItem + == our::stringification::MustModelTypeFromString( + "SpecialItem" + ) + ); + + REQUIRE( + our::stringification::to_string( + our::types::ModelType::kSpecialItem + ) + == "SpecialItem" + ); + + REQUIRE( + our::types::ModelType::kContainer + == our::stringification::MustModelTypeFromString( + "Container" + ) + ); + + REQUIRE( + our::stringification::to_string( + our::types::ModelType::kContainer + ) + == "Container" + ); + + REQUIRE( + our::types::ModelType::kWrapper + == our::stringification::MustModelTypeFromString( + "Wrapper" + ) + ); + + REQUIRE( + our::stringification::to_string( + our::types::ModelType::kWrapper + ) + == "Wrapper" + ); +} + +TEST_CASE("Test failure on ModelType") { + CHECK( + !our::stringification::ModelTypeFromString( + "Totally utterly invalid" + ).has_value() + ); + + REQUIRE_THROWS_WITH( + our::stringification::MustModelTypeFromString( + "Totally utterly invalid" + ), + "Unexpected ModelType literal: Totally utterly invalid" + ); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_verification.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_verification.cpp new file mode 100644 index 000000000..bd9d7d7fb --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_verification.cpp @@ -0,0 +1,324 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_xmlization.hpp" + +#include +#include + +#include + +#include + +namespace our = dummy; + +void AssertNoVerificationError( + const std::filesystem::path& xml_path +) { + std::shared_ptr< + our::types::IClass + > instance( + test::common::xmlization::MustDeserializeFile(xml_path) + ); + + std::vector error_messages; + for ( + const our::verification::Error &error + : our::verification::RecursiveVerification(instance) + ) { + error_messages.emplace_back( + our::common::Concat( + our::common::WstringToUtf8(error.path.ToWstring()), + ": ", + our::common::WstringToUtf8(error.cause) + ) + ); + } + + if (!error_messages.empty()) { + std::vector parts; + parts.emplace_back("Expected no error messages from "); + parts.emplace_back(xml_path.string()); + parts.emplace_back(", but got:\n"); + parts.emplace_back(test::common::JoinStrings(error_messages, "\n")); + + INFO(test::common::JoinStrings(parts, "")) + CHECK(error_messages.empty()); + } +} + +const std::filesystem::path& DetermineXmlDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "Xml"; + } + + return *result; +} + +const std::filesystem::path& DetermineErrorDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "VerificationError"; + } + + return *result; +} + +void AssertVerificationFailure( + const std::filesystem::path& path, + const std::filesystem::path& error_path +) { + std::ifstream ifs(path, std::ios::binary); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From( + ifs + ); + + if (!deserialized.has_value()) { + INFO( + our::common::Concat( + "Expected to de-serialize ", + path.string(), + ", but the de-serialization failed: ", + our::common::WstringToUtf8(deserialized.error().path.ToWstring()), + our::common::WstringToUtf8(deserialized.error().cause) + ) + ) + REQUIRE(!deserialized.has_value()); + } + + std::vector error_messages; + for ( + const our::verification::Error& error + : our::verification::RecursiveVerification(deserialized.value()) + ) { + error_messages.emplace_back( + our::common::Concat( + our::common::WstringToUtf8(error.path.ToWstring()), + ": ", + our::common::WstringToUtf8(error.cause) + ) + ); + } + + if (error_messages.empty()) { + INFO( + our::common::Concat( + "Expected error messages from ", + path.string(), + ", but got none" + ) + ) + REQUIRE(!error_messages.empty()); + } + + const std::string joined_error_messages = test::common::JoinStrings( + error_messages, + "\n" + ); + + test::common::AssertContentEqualsExpectedOrRecord( + joined_error_messages, + error_path + ); +} + +TEST_CASE("Test verification of a valid SomeItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "someItem", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertNoVerificationError(path); + } +} + +TEST_CASE("Test verification of invalid cases for SomeItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Invalid" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "someItem", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".errors") + ); + + AssertVerificationFailure(path, error_path); + } + } +} + +TEST_CASE("Test verification of a valid SpecialItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "specialItem", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertNoVerificationError(path); + } +} + +TEST_CASE("Test verification of invalid cases for SpecialItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Invalid" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "specialItem", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".errors") + ); + + AssertVerificationFailure(path, error_path); + } + } +} + +TEST_CASE("Test verification of a valid Container") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "container", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertNoVerificationError(path); + } +} + +TEST_CASE("Test verification of invalid cases for Container") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Invalid" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "container", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".errors") + ); + + AssertVerificationFailure(path, error_path); + } + } +} + +TEST_CASE("Test verification of a valid Wrapper") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "wrapper", + ".xml" + ) + ); + + for (const std::filesystem::path& path : paths) { + AssertNoVerificationError(path); + } +} + +TEST_CASE("Test verification of invalid cases for Wrapper") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Invalid" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "wrapper", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".errors") + ); + + AssertVerificationFailure(path, error_path); + } + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_wstringification_of_enums.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_wstringification_of_enums.cpp new file mode 100644 index 000000000..4da059f3c --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_wstringification_of_enums.cpp @@ -0,0 +1,84 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "dummy/wstringification.hpp" + +#include + +namespace our = dummy; + +TEST_CASE("Test ModelType round-trip") { + REQUIRE( + our::types::ModelType::kSomeItem + == our::wstringification::MustModelTypeFromWstring( + L"SomeItem" + ) + ); + + REQUIRE( + our::wstringification::to_wstring( + our::types::ModelType::kSomeItem + ) + == L"SomeItem" + ); + + REQUIRE( + our::types::ModelType::kSpecialItem + == our::wstringification::MustModelTypeFromWstring( + L"SpecialItem" + ) + ); + + REQUIRE( + our::wstringification::to_wstring( + our::types::ModelType::kSpecialItem + ) + == L"SpecialItem" + ); + + REQUIRE( + our::types::ModelType::kContainer + == our::wstringification::MustModelTypeFromWstring( + L"Container" + ) + ); + + REQUIRE( + our::wstringification::to_wstring( + our::types::ModelType::kContainer + ) + == L"Container" + ); + + REQUIRE( + our::types::ModelType::kWrapper + == our::wstringification::MustModelTypeFromWstring( + L"Wrapper" + ) + ); + + REQUIRE( + our::wstringification::to_wstring( + our::types::ModelType::kWrapper + ) + == L"Wrapper" + ); +} + +TEST_CASE("Test failure on ModelType") { + CHECK( + !our::wstringification::ModelTypeFromWstring( + L"Totally utterly invalid" + ).has_value() + ); + + REQUIRE_THROWS_WITH( + our::wstringification::MustModelTypeFromWstring( + L"Totally utterly invalid" + ), + "Unexpected ModelType literal: Totally utterly invalid" + ); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_x_or_default.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_x_or_default.cpp new file mode 100644 index 000000000..08d0b32cd --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_x_or_default.cpp @@ -0,0 +1,24 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_examples.hpp" + +#include +#include + +#include + +namespace our = dummy; + +const std::filesystem::path& DetermineLogDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "XxxOrDefault"; + } + + return *result; +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_dispatch.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_dispatch.cpp new file mode 100644 index 000000000..357e71770 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_dispatch.cpp @@ -0,0 +1,149 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common_examples.hpp" + +#include + +#include + +namespace our = dummy; + +TEST_CASE("Test the round-trip of an expected IItem") { + const std::shared_ptr< + our::types::ISomeItem + > original_instance( + test::common::examples::LoadMinSomeItem() + ); + + std::stringstream ss; + our::xmlization::Serialize( + *original_instance, + {}, + ss + ); + + const std::string original_xml = ss.str(); + + ss.seekp(0); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From( + ss + ); + + if (!deserialized.has_value()) { + INFO( + our::common::Concat( + "Failed to make the round-trip Serialize-Deserialize " + "a minimal instance of ISomeItem: ", + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ) + ) + REQUIRE(deserialized.has_value()); + } + + std::shared_ptr< + our::types::IItem + > abstract = std::dynamic_pointer_cast< + our::types::IItem + >(deserialized.value()); + + std::stringstream another_ss; + our::xmlization::Serialize( + *abstract, + {}, + another_ss + ); + + INFO( + "Original XML and the XML at the end of " + "the chain Serialize-Deserialize-Serialize for " + "a minimal instance of ISomeItem " + "serialized as IItem " + "must coincide" + ) + REQUIRE( + original_xml + == another_ss.str() + ); +} + +TEST_CASE("Test the round-trip of an expected ISomeItem") { + const std::shared_ptr< + our::types::ISpecialItem + > original_instance( + test::common::examples::LoadMinSpecialItem() + ); + + std::stringstream ss; + our::xmlization::Serialize( + *original_instance, + {}, + ss + ); + + const std::string original_xml = ss.str(); + + ss.seekp(0); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From( + ss + ); + + if (!deserialized.has_value()) { + INFO( + our::common::Concat( + "Failed to make the round-trip Serialize-Deserialize " + "a minimal instance of ISpecialItem: ", + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ) + ) + REQUIRE(deserialized.has_value()); + } + + std::shared_ptr< + our::types::ISomeItem + > abstract = std::dynamic_pointer_cast< + our::types::ISomeItem + >(deserialized.value()); + + std::stringstream another_ss; + our::xmlization::Serialize( + *abstract, + {}, + another_ss + ); + + INFO( + "Original XML and the XML at the end of " + "the chain Serialize-Deserialize-Serialize for " + "a minimal instance of ISpecialItem " + "serialized as ISomeItem " + "must coincide" + ) + REQUIRE( + original_xml + == another_ss.str() + ); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_of_concrete_classes.cpp b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_of_concrete_classes.cpp new file mode 100644 index 000000000..0bbfe3c79 --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/expected_output/test/test_xmlization_of_concrete_classes.cpp @@ -0,0 +1,349 @@ +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +#include "./common.hpp" +#include "./common_xmlization.hpp" + +#include + +#include + +#include + +namespace our = dummy; + +void AssertRoundTrip( + const std::filesystem::path& path +) { + std::shared_ptr< + our::types::IClass + > deserialized( + test::common::xmlization::MustDeserializeFile(path) + ); + + std::stringstream ss; + our::xmlization::Serialize(*deserialized, {}, ss); + + std::string expected_xml = test::common::MustReadString(path); + + INFO(our::common::Concat("XML round-trip on ", path.string())) + REQUIRE( + test::common::xmlization::CanonicalizeXml(expected_xml) + == test::common::xmlization::CanonicalizeXml(ss.str()) + ); +} + +void AssertDeserializationFailure( + const std::filesystem::path& path, + const std::filesystem::path& error_path +) { + std::ifstream ifs(path, std::ios::binary); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From( + ifs + ); + + if (deserialized.has_value()) { + INFO( + our::common::Concat( + "Expected the de-serialization to fail on ", + path.string(), + ", but the de-serialization succeeded" + ) + ) + REQUIRE(!deserialized.has_value()); + } + + test::common::AssertContentEqualsExpectedOrRecord( + our::common::Concat( + our::common::WstringToUtf8( + deserialized.error().path.ToWstring() + ), + ": ", + our::common::WstringToUtf8( + deserialized.error().cause + ) + ), + error_path + ); +} + +const std::filesystem::path& DetermineXmlDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "Xml"; + } + + return *result; +} + +const std::filesystem::path& DetermineErrorDir() { + static our::common::optional result; + if (!result.has_value()) { + result = test::common::DetermineTestDataDir() / "XmlizationError"; + } + + return *result; +} + +TEST_CASE("Test the round-trip of an expected SomeItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "someItem", + ".xml" + ) + ); + + for (const std::filesystem::path &path : paths) { + AssertRoundTrip(path); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected SomeItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "someItem", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure( + path, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected SpecialItem") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "specialItem", + ".xml" + ) + ); + + for (const std::filesystem::path &path : paths) { + AssertRoundTrip(path); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected SpecialItem") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "specialItem", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure( + path, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected Container") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "container", + ".xml" + ) + ); + + for (const std::filesystem::path &path : paths) { + AssertRoundTrip(path); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected Container") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "container", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure( + path, + error_path + ); + } + } +} + +TEST_CASE("Test the round-trip of an expected Wrapper") { + const std::deque paths( + test::common::FindFilesBySuffixRecursively( + DetermineXmlDir() + / "Expected" + / "wrapper", + ".xml" + ) + ); + + for (const std::filesystem::path &path : paths) { + AssertRoundTrip(path); + } +} + +TEST_CASE("Test the de-serialization failure on an unexpected Wrapper") { + for ( + const std::filesystem::path& causeDir + : test::common::ListSubdirectories( + DetermineXmlDir() + / "Unexpected" + / "Unserializable" + ) + ) { + for ( + const std::filesystem::path& path + : test::common::FindFilesBySuffixRecursively( + causeDir / "wrapper", + ".xml" + ) + ) { + const std::filesystem::path parent( + ( + DetermineErrorDir() + / std::filesystem::relative(path, DetermineXmlDir()) + ).parent_path() + ); + + const std::filesystem::path error_path( + parent + / (path.filename().string() + ".error") + ); + + AssertDeserializationFailure( + path, + error_path + ); + } + } +} + +TEST_CASE("Test the de-serialization failure on a duplicate property") { + const std::filesystem::path path( + DetermineXmlDir() + / "Expected" + / "someItem" + / "minimal.xml" + ); + + const std::string original(test::common::MustReadString(path)); + + // We cut the element of the property out of the recorded example and put it + // in a second time, just before the closing tag of the instance. + const std::size_t start( + original.find("") + ); + + std::string property; + if (start != std::string::npos) { + const std::size_t end( + original.find("", start) + ); + property = original.substr(start, end + 7 - start); + } else { + // The element is written self-closing in the example, an empty list being + // the usual reason. We write that very element out ourselves. + property = ""; + } + + const std::size_t insertion_index( + original.rfind("") + ); + + INFO(our::common::Concat("Looking for in ", path.string())) + REQUIRE(insertion_index != std::string::npos); + + const std::string broken( + original.substr(0, insertion_index) + + property + + original.substr(insertion_index) + ); + + std::istringstream iss(broken); + + our::common::expected< + std::shared_ptr, + our::xmlization::DeserializationError + > deserialized = our::xmlization::From(iss); + + INFO(our::common::Concat("De-serializing: ", broken)) + REQUIRE(!deserialized.has_value()); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/cpp/expected/variance_of_containers/input/snippets/namespace.txt b/dev/test_data/main/cpp/expected/variance_of_containers/input/snippets/namespace.txt new file mode 100644 index 000000000..2995a4d0e --- /dev/null +++ b/dev/test_data/main/cpp/expected/variance_of_containers/input/snippets/namespace.txt @@ -0,0 +1 @@ +dummy \ No newline at end of file diff --git a/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/return_code.json b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/return_code.json new file mode 100644 index 000000000..56a6051ca --- /dev/null +++ b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/return_code.json @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stderr.txt b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stderr.txt new file mode 100644 index 000000000..322f3d6f5 --- /dev/null +++ b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stderr.txt @@ -0,0 +1,2 @@ +Failed to generate include/dummy/verification.hpp based on : +* At line 35 and column 17: The public verification function 'items_are_named' has generic sequences, so we generate it in C++ as a template defined in the header. However, it calls the internal verification function '_is_named', which is declared only in the implementation file. Please make '_is_named' public, or 'items_are_named' internal. diff --git a/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stdout.txt b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/expected_output/stdout.txt new file mode 100644 index 000000000..e69de29bb diff --git a/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/input/snippets/namespace.txt b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/input/snippets/namespace.txt new file mode 100644 index 000000000..2995a4d0e --- /dev/null +++ b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/input/snippets/namespace.txt @@ -0,0 +1 @@ +dummy \ No newline at end of file diff --git a/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/meta_model.py b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/meta_model.py new file mode 100644 index 000000000..3ea7f3b94 --- /dev/null +++ b/dev/test_data/main/cpp/unexpected/public_generic_verification_calls_internal/meta_model.py @@ -0,0 +1,50 @@ +""" +Test that a public generic verification function can not call an internal one. + +The public generic verification functions are templates defined in the C++ header, +while the internal verification functions are declared only in the implementation +file. +""" + +from typing import List, Sequence + +from icontract import DBC, invariant + + +@abstract +@serialization(with_model_type=True) +class Item(DBC): + name: str + + def __init__(self, name: str) -> None: + self.name = name + + +class Some_item(Item, DBC): + def __init__(self, name: str) -> None: + Item.__init__(self, name) + + +@verification +def _is_named(item: Item) -> bool: + return len(item.name) > 0 + + +@verification +def items_are_named(items: Sequence[Item]) -> bool: + return all(_is_named(item) for item in items) + + +@invariant( + lambda self: items_are_named(self.items), + "The items must be named.", +) +class Container(DBC): + items: List[Some_item] + + def __init__(self, items: List[Some_item]) -> None: + self.items = items + + +__version__ = "dummy" +__xml_namespace__ = "https://dummy.com" diff --git a/dev/test_data/main/csharp/expected/annotated_assignments/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/annotated_assignments/expected_output/dummy/verification.cs index ba92418a8..b7bef033d 100644 --- a/dev/test_data/main/csharp/expected/annotated_assignments/expected_output/dummy/verification.cs +++ b/dev/test_data/main/csharp/expected/annotated_assignments/expected_output/dummy/verification.cs @@ -34,8 +34,11 @@ public static class Verification /// /// Check an optional class declared with None and assigned in a loop. /// + /// + /// The argument parents is declared as the covariant IReadOnlyList<IParent> instead of the invariant List<IParent> so that the lists of the descendants of IParent can be passed without a copy. + /// public static bool FirstChildAIsSmall( - List parents + IReadOnlyList parents ) { Our.IChildA? found = null; @@ -91,9 +94,12 @@ public static bool TextIsAllowedUnlessLong( /// /// Check a non-optional variable declared wider than its value. /// + /// + /// The argument parents is declared as the covariant IReadOnlyList<IParent> instead of the invariant List<IParent> so that the lists of the descendants of IParent can be passed without a copy. + /// public static bool LastChildBIsNotZero( IParent parent, - List parents + IReadOnlyList parents ) { Our.IParent last = parent; @@ -374,9 +380,9 @@ IParent parent ) { long limit = 1000; - string label = text; - string? maybeText = optionalText; - List allTexts = texts; + var label = text; + var maybeText = optionalText; + var allTexts = texts; HashSet seen = new HashSet(); seen.Add(text); HashSet reservedSeen = ( @@ -385,7 +391,7 @@ IParent parent text, number ); - Our.IParent basE = parent; + var basE = parent; if ( reservedSeen.Count > 0 || Common.StringHelpers.Len(label) > limit diff --git a/dev/test_data/main/csharp/expected/if_statements/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/if_statements/expected_output/dummy/verification.cs index 7a2f5fa69..a08dfbbe8 100644 --- a/dev/test_data/main/csharp/expected/if_statements/expected_output/dummy/verification.cs +++ b/dev/test_data/main/csharp/expected/if_statements/expected_output/dummy/verification.cs @@ -296,8 +296,11 @@ public static bool NarrowingAfterTheOnlyCompletingBranch( /// /// Check the narrowing after the continue and the early return in a loop. /// + /// + /// The argument parents is declared as the covariant IReadOnlyList<IParent> instead of the invariant List<IParent> so that the lists of the descendants of IParent can be passed without a copy. + /// public static bool ChildAsHaveTexts( - List parents + IReadOnlyList parents ) { foreach (var parent in parents) @@ -321,8 +324,11 @@ List parents /// /// Check the narrowing after the continue in a loop with a break. /// + /// + /// The argument parents is declared as the covariant IReadOnlyList<IParent> instead of the invariant List<IParent> so that the lists of the descendants of IParent can be passed without a copy. + /// public static bool TextsBeforeContainerAreShort( - List parents + IReadOnlyList parents ) { long total = 0; @@ -360,9 +366,12 @@ IParent parent /// /// Check the narrowing of a variable to a class by the assigned value. /// + /// + /// The argument parents is declared as the covariant IReadOnlyList<IParent> instead of the invariant List<IParent> so that the lists of the descendants of IParent can be passed without a copy. + /// public static bool LastChildAIsSmall( IParent parent, - List parents + IReadOnlyList parents ) { var last = parent; diff --git a/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs index 2b908b28e..dac0d3bcf 100644 --- a/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs +++ b/dev/test_data/main/csharp/expected/list_of_classes/expected_output/dummy/verification.cs @@ -66,11 +66,16 @@ List simples /// Give the instances of among . /// /// + /// /// The appended items are narrowed down to , while the list /// holds 's. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IAbstractItem> instead of the invariant List<IAbstractItem> so that the lists of the descendants of IAbstractItem can be passed without a copy. + /// /// public static List SomeItemsOf( - List items + IReadOnlyList items ) { List result = new List(); diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/Common.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/Common.cs new file mode 100644 index 000000000..13511cd53 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/Common.cs @@ -0,0 +1,156 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + /// + /// Provide common functionality to be re-used across different tests + /// such as reading of environment variables. + /// + public static class Common + { + public static readonly string RecordModeEnvironmentVariableName = ( + "DUMMY_TESTS_RECORD_MODE" + ); + + // NOTE (mristin): + // It is tedious to record manually all the expected error messages. + // Therefore we include this variable to steer the automatic recording. + // We intentionally inter-twine the recording code with the test code + // to keep them close to each other so that they are easier to maintain. + public static readonly bool RecordMode = ( + System.Environment.GetEnvironmentVariable( + RecordModeEnvironmentVariableName + )?.ToLower() + == "true" + || System + .Environment.GetEnvironmentVariable(RecordModeEnvironmentVariableName) + ?.ToLower() == "on" + || System + .Environment.GetEnvironmentVariable(RecordModeEnvironmentVariableName) + ?.ToLower() == "1" + || System + .Environment.GetEnvironmentVariable(RecordModeEnvironmentVariableName) + ?.ToLower() == "yes" + ); + + public static readonly string TestDataDir = ( + System.Environment.GetEnvironmentVariable( + "DUMMY_TESTS_TEST_DATA_DIR" + ) + ?? throw new System.InvalidOperationException( + "The path to the test data directory is missing in the environment: " + + "DUMMY_TESTS_TEST_DATA_DIR" + ) + ); + + /// + /// Find the first instance of + /// in the , + /// including the itself. + /// + public static T MustFind(Our.IClass container) + where T : Our.IClass + { + var instance = ( + (container is T) + ? container + : container.Descend().First(something => something is T) + ?? throw new System.InvalidOperationException( + $"No instance of {nameof(T)} could be found" + ) + ); + + return (T)instance; + } + + public static void AssertNoVerificationErrors( + List errors, + string path + ) + { + if (errors.Count > 0) + { + var builder = new System.Text.StringBuilder(); + builder.Append( + $"Expected no errors when verifying the instance de-serialized from {path}, " + + $"but got {errors.Count} error(s):\n" + ); + for (var i = 0; i < errors.Count; i++) + { + builder.Append( + $"Error {i + 1}:\n" + + $"{Reporting.GenerateJsonPath(errors[i].PathSegments)}: " + + $"{errors[i].Cause}\n" + ); + } + + Assert.Fail(builder.ToString()); + } + } + + public static void AssertEqualsExpectedOrRerecordVerificationErrors( + List errors, + string path + ) + { + if (errors.Count == 0) + { + Assert.Fail( + $"Expected at least one verification error when verifying {path}, " + + "but got none" + ); + } + + string got = string.Join( + ";\n", + errors.Select(error => + $"{Reporting.GenerateJsonPath(error.PathSegments)}: {error.Cause}" + ) + ); + + string errorsPath = path + ".errors"; + if (RecordMode) + { + System.IO.File.WriteAllText(errorsPath, got); + } + else + { + if (!System.IO.File.Exists(errorsPath)) + { + throw new System.IO.FileNotFoundException( + "The file with the recorded errors does not " + + $"exist: {errorsPath}; maybe you want to set " + + "the environment variable " + + $"{Our.Tests.Common.RecordModeEnvironmentVariableName}?" + ); + } + + string expected = System.IO.File.ReadAllText(errorsPath); + Assert.AreEqual( + expected.Replace("\r\n", "\n"), + got.Replace("\r\n", "\n"), + $"The expected verification errors do not match the actual ones for the file {path}" + ); + } + } + + public static string Trace(Our.IClass instance) + { + return instance.GetType().Name; + } + } // public static class Common +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJson.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJson.cs new file mode 100644 index 000000000..a9b32101f --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJson.cs @@ -0,0 +1,222 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using FileMode = System.IO.FileMode; +using FileStream = System.IO.FileStream; +using JsonException = System.Text.Json.JsonException; +using Nodes = System.Text.Json.Nodes; + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias + +namespace dummy.Tests +{ + public static class CommonJson + { + public static Nodes.JsonNode ReadFromFile(string path) + { + using var stream = new FileStream(path, FileMode.Open); + Nodes.JsonNode? node; + try + { + node = Nodes.JsonNode.Parse(stream); + } + catch (JsonException exception) + { + throw new System.InvalidOperationException( + $"Expected the file to be a valid JSON, but it was not: {path}; " + + $"exception was: {exception}" + ); + } + + if (node is null) + { + throw new System.InvalidOperationException( + "Expected the file to be a non-null JSON value, " + $"but it was null: {path}" + ); + } + + return node; + } + + /// + /// Serialize to a uniform JSON text + /// such that we can use it for comparisons in the tests. + /// + public static Nodes.JsonNode ToJson(object something) + { + switch (something) + { + case bool aBool: + return Nodes.JsonValue.Create(aBool); + case long aLong: + return Nodes.JsonValue.Create(aLong); + case double aDouble: + return Nodes.JsonValue.Create(aDouble); + case string aString: + return Nodes.JsonValue.Create(aString) + ?? throw new System.InvalidOperationException( + $"Could not convert {something} " + "to a JSON string" + ); + case byte[] someBytes: + return Nodes.JsonValue.Create(System.Convert.ToBase64String(someBytes)) + ?? throw new System.InvalidOperationException( + $"Could not convert {something} to " + "a base64-encoded JSON string" + ); + case Our.IClass instance: + return Our.Jsonization.Serialize.ToJsonObject(instance); + default: + throw new System.ArgumentException( + $"The conversion of type {something.GetType()} " + + $"to a JSON node has not been defined: {something}" + ); + } + } + + /// + /// Infer the node kind of the JSON node. + /// + /// + /// This function is necessary since NET6 does not fully support node kinds yet. + /// See: + ///
    + ///
  • https://github.com/dotnet/runtime/issues/53406
  • + ///
  • https://github.com/dotnet/runtime/issues/55827
  • + ///
  • https://github.com/dotnet/runtime/issues/56592
  • + ///
+ ///
+ private static string GetNodeKind(Nodes.JsonNode node) + { + switch (node) + { + case Nodes.JsonArray _: + return "array"; + case Nodes.JsonObject _: + return "object"; + case Nodes.JsonValue _: + return "value"; + default: + throw new System.InvalidOperationException( + $"Unhandled JsonNode: {node.GetType()}" + ); + } + } + + public static void CheckJsonNodesEqual( + Nodes.JsonNode that, + Nodes.JsonNode other, + out Reporting.Error? error + ) + { + error = null; + + var thatNodeKind = GetNodeKind(that); + var otherNodeKind = GetNodeKind(other); + + if (thatNodeKind != otherNodeKind) + { + error = new Reporting.Error( + $"Mismatch in node kinds : {thatNodeKind} != {otherNodeKind}" + ); + return; + } + + switch (that) + { + case Nodes.JsonArray thatArray: + { + var otherArray = (other as Nodes.JsonArray)!; + if (thatArray.Count != otherArray.Count) + { + error = new Reporting.Error( + $"Unequal array lengths: {thatArray.Count} != {otherArray.Count}" + ); + return; + } + + for (int i = 0; i < thatArray.Count; i++) + { + CheckJsonNodesEqual(thatArray[i]!, otherArray[i]!, out error); + if (error != null) + { + error.PrependSegment(new Reporting.IndexSegment(i)); + return; + } + } + + break; + } + case Nodes.JsonObject thatObject: + { + var thatDictionary = thatObject as IDictionary; + var otherDictionary = (other as IDictionary)!; + + var thatKeys = thatDictionary.Keys.ToList(); + thatKeys.Sort(); + + var otherKeys = otherDictionary.Keys.ToList(); + otherKeys.Sort(); + + if (!thatKeys.SequenceEqual(otherKeys)) + { + error = new Reporting.Error( + "Objects with different properties: " + + $"{string.Join(", ", thatKeys)} != " + + $"{string.Join(", ", otherKeys)}" + ); + return; + } + + foreach (var key in thatKeys) + { + CheckJsonNodesEqual(thatDictionary[key], otherDictionary[key], out error); + if (error != null) + { + error.PrependSegment(new Reporting.NameSegment(key)); + return; + } + } + + break; + } + case Nodes.JsonValue thatValue: + { + string thatAsJsonString = thatValue.ToJsonString(); + + // NOTE (mristin): + // This is slow, but there is no way around it at the moment with NET6. + // See: + // * https://github.com/dotnet/runtime/issues/56592 + // * https://github.com/dotnet/runtime/issues/55827 + // * https://github.com/dotnet/runtime/issues/53406 + var otherValue = (other as Nodes.JsonValue)!; + string otherAsJsonString = otherValue.ToJsonString(); + + if (thatAsJsonString != otherAsJsonString) + { + error = new Reporting.Error( + $"Unequal values: {thatAsJsonString} != {otherAsJsonString}" + ); + // ReSharper disable once RedundantJumpStatement + return; + } + + break; + } + default: + throw new System.InvalidOperationException( + $"Unhandled JSON node: {that.GetType()}" + ); + } + } + } // public static class CommonJson +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJsonization.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJsonization.cs new file mode 100644 index 000000000..42fbc331b --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/CommonJsonization.cs @@ -0,0 +1,158 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Path = System.IO.Path; + +namespace dummy.Tests +{ + /// + /// Provide methods to load instances from JSON test data. + /// + public static class CommonJsonization + { + public static Our.SomeItem LoadMaximalSomeItem() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SomeItem", + "maximal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SomeItemFrom( + node); + + return instance; + } // public static Our.SomeItem LoadMaximalSomeItem + + public static Our.SomeItem LoadMinimalSomeItem() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SomeItem", + "minimal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SomeItemFrom( + node); + + return instance; + } // public static Our.SomeItem LoadMinimalSomeItem + + public static Our.SpecialItem LoadMaximalSpecialItem() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SpecialItem", + "maximal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + + return instance; + } // public static Our.SpecialItem LoadMaximalSpecialItem + + public static Our.SpecialItem LoadMinimalSpecialItem() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SpecialItem", + "minimal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + + return instance; + } // public static Our.SpecialItem LoadMinimalSpecialItem + + public static Our.Container LoadMaximalContainer() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Container", + "maximal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.ContainerFrom( + node); + + return instance; + } // public static Our.Container LoadMaximalContainer + + public static Our.Container LoadMinimalContainer() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Container", + "minimal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.ContainerFrom( + node); + + return instance; + } // public static Our.Container LoadMinimalContainer + + public static Our.Wrapper LoadMaximalWrapper() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Wrapper", + "maximal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.WrapperFrom( + node); + + return instance; + } // public static Our.Wrapper LoadMaximalWrapper + + public static Our.Wrapper LoadMinimalWrapper() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Wrapper", + "minimal.json"); + + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.WrapperFrom( + node); + + return instance; + } // public static Our.Wrapper LoadMinimalWrapper + } // class CommonJsonization +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestCopying.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestCopying.cs new file mode 100644 index 000000000..a62b0c55c --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestCopying.cs @@ -0,0 +1,335 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +// We need to use System.MemoryExtension.SequenceEqual. +using System; // can't alias +using System.Collections.Generic; // can't alias + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestCopying + { + /// + /// Check that the context is a deep copy of the visited instance. + /// + /// + /// The copy has to equal the original by value, while it must share none + /// of the mutable objects with it. + /// + internal class DeepCopyChecker + : Our.Visitation.AbstractTransformerWithContext + { + private static bool BytesEqualButDistinct( + byte[] that, + byte[] other) + { + // NOTE (mristin): + // A byte[] implicitly converts to a ReadOnlySpan, which compares by content. + // See: https://stackoverflow.com/a/48599119/1600678 + return ( + !ReferenceEquals(that, other) + && ((System.ReadOnlySpan)that).SequenceEqual(other)); + } + + public override bool TransformSomeItem( + Our.ISomeItem that, + Our.IClass other) + { + if (!(other is Our.SomeItem casted) || ReferenceEquals(that, other)) + { + return false; + } + + return ( + that.Name == casted.Name + && ((that.Nickname != null && casted.Nickname != null) + ? that.Nickname == casted.Nickname + : that.Nickname == null && casted.Nickname == null)); + } + + public override bool TransformSpecialItem( + Our.ISpecialItem that, + Our.IClass other) + { + if (!(other is Our.SpecialItem casted) || ReferenceEquals(that, other)) + { + return false; + } + + return ( + that.Name == casted.Name + && ((that.Nickname != null && casted.Nickname != null) + ? that.Nickname == casted.Nickname + : that.Nickname == null && casted.Nickname == null) + && that.Level == casted.Level); + } + + public override bool TransformContainer( + Our.IContainer that, + Our.IClass other) + { + if (!(other is Our.Container casted) || ReferenceEquals(that, other)) + { + return false; + } + + return ( + Check_ListOf_IItem( + that.Items, + casted.Items) + && Check_ListOf_ISomeItem( + that.SomeItems, + casted.SomeItems) + && ((that.SpecialItems != null && casted.SpecialItems != null) + ? Check_ListOf_ISpecialItem( + that.SpecialItems, + casted.SpecialItems) + : that.SpecialItems == null && casted.SpecialItems == null)); + } + + public override bool TransformWrapper( + Our.IWrapper that, + Our.IClass other) + { + if (!(other is Our.Wrapper casted) || ReferenceEquals(that, other)) + { + return false; + } + + return ( + Transform( + that.Container, + casted.Container)); + } + + private bool Check_ListOf_IItem( + List that, + List other) + { + if (ReferenceEquals(that, other) || that.Count != other.Count) + { + return false; + } + + for (int i = 0; i < that.Count; i++) + { + if (!( + Transform( + that[i], + other[i]))) + { + return false; + } + } + + return true; + } + + private bool Check_ListOf_ISomeItem( + List that, + List other) + { + if (ReferenceEquals(that, other) || that.Count != other.Count) + { + return false; + } + + for (int i = 0; i < that.Count; i++) + { + if (!( + Transform( + that[i], + other[i]))) + { + return false; + } + } + + return true; + } + + private bool Check_ListOf_ISpecialItem( + List that, + List other) + { + if (ReferenceEquals(that, other) || that.Count != other.Count) + { + return false; + } + + for (int i = 0; i < that.Count; i++) + { + if (!( + Transform( + that[i], + other[i]))) + { + return false; + } + } + + return true; + } + } // internal class DeepCopyChecker + + private static readonly DeepCopyChecker DeepCopyCheckerInstance = ( + new DeepCopyChecker()); + + private static bool SomeItemShallowEquals( + Our.SomeItem that, + Our.SomeItem other) + { + return that.Name == other.Name && that.Nickname == other.Nickname; + } + + private static bool SpecialItemShallowEquals( + Our.SpecialItem that, + Our.SpecialItem other) + { + return ( + that.Name == other.Name + && that.Nickname == other.Nickname + && that.Level == other.Level); + } + + private static bool ContainerShallowEquals( + Our.Container that, + Our.Container other) + { + return ( + that.Items == other.Items + && that.SomeItems == other.SomeItems + && that.SpecialItems == other.SpecialItems); + } + + private static bool WrapperShallowEquals( + Our.Wrapper that, + Our.Wrapper other) + { + return that.Container == other.Container; + } + + [Test] + public void Test_SomeItem_shallow_copy() + { + Our.SomeItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem()); + + var instanceCopy = Our.Copying.Shallow(instance); + + Assert.IsTrue( + SomeItemShallowEquals( + instance, instanceCopy), + "SomeItem"); + } // public void Test_SomeItem_shallow_copy + + [Test] + public void Test_SomeItem_deep_copy() + { + Our.SomeItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem()); + + var instanceCopy = Our.Copying.Deep(instance); + + Assert.IsTrue( + DeepCopyCheckerInstance.Transform( + instance, instanceCopy), + "SomeItem"); + } // public void Test_SomeItem_deep_copy + + [Test] + public void Test_SpecialItem_shallow_copy() + { + Our.SpecialItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem()); + + var instanceCopy = Our.Copying.Shallow(instance); + + Assert.IsTrue( + SpecialItemShallowEquals( + instance, instanceCopy), + "SpecialItem"); + } // public void Test_SpecialItem_shallow_copy + + [Test] + public void Test_SpecialItem_deep_copy() + { + Our.SpecialItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem()); + + var instanceCopy = Our.Copying.Deep(instance); + + Assert.IsTrue( + DeepCopyCheckerInstance.Transform( + instance, instanceCopy), + "SpecialItem"); + } // public void Test_SpecialItem_deep_copy + + [Test] + public void Test_Container_shallow_copy() + { + Our.Container instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer()); + + var instanceCopy = Our.Copying.Shallow(instance); + + Assert.IsTrue( + ContainerShallowEquals( + instance, instanceCopy), + "Container"); + } // public void Test_Container_shallow_copy + + [Test] + public void Test_Container_deep_copy() + { + Our.Container instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer()); + + var instanceCopy = Our.Copying.Deep(instance); + + Assert.IsTrue( + DeepCopyCheckerInstance.Transform( + instance, instanceCopy), + "Container"); + } // public void Test_Container_deep_copy + + [Test] + public void Test_Wrapper_shallow_copy() + { + Our.Wrapper instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper()); + + var instanceCopy = Our.Copying.Shallow(instance); + + Assert.IsTrue( + WrapperShallowEquals( + instance, instanceCopy), + "Wrapper"); + } // public void Test_Wrapper_shallow_copy + + [Test] + public void Test_Wrapper_deep_copy() + { + Our.Wrapper instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper()); + + var instanceCopy = Our.Copying.Deep(instance); + + Assert.IsTrue( + DeepCopyCheckerInstance.Transform( + instance, instanceCopy), + "Wrapper"); + } // public void Test_Wrapper_deep_copy + } // class TestCopying +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendAndVisitorThrough.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendAndVisitorThrough.cs new file mode 100644 index 000000000..60d25a7b4 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendAndVisitorThrough.cs @@ -0,0 +1,201 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Directory = System.IO.Directory; +using Path = System.IO.Path; + +using NUnit.Framework; // can't alias +using System.Collections.Generic; // can't alias + +namespace dummy.Tests +{ + public class TestDescendAndVisitorThrough + { + class TracingVisitorThrough : Our.Visitation.VisitorThrough + { + public readonly List Log = new List(); + + public override void Visit(IClass that) + { + Log.Add(Our.Tests.Common.Trace(that)); + base.Visit(that); + } + } + + private static void AssertDescendAndVisitorThroughSame( + Our.IClass instance) + { + var logFromDescend = new List(); + foreach (var subInstance in instance.Descend()) + { + logFromDescend.Add(Our.Tests.Common.Trace(subInstance)); + } + + var visitor = new TracingVisitorThrough(); + visitor.Visit(instance); + var traceFromVisitor = visitor.Log; + + Assert.IsNotEmpty(traceFromVisitor); + + Assert.AreEqual( + Our.Tests.Common.Trace(instance), + traceFromVisitor[0]); + + traceFromVisitor.RemoveAt(0); + + Assert.That(traceFromVisitor, Is.EquivalentTo(logFromDescend)); + } + + private static void CompareOrRerecordTrace( + IClass instance, + string expectedPath) + { + var writer = new System.IO.StringWriter(); + foreach (var descendant in instance.Descend()) + { + writer.WriteLine(Our.Tests.Common.Trace(descendant)); + } + + string got = writer.ToString(); + + if (Our.Tests.Common.RecordMode) + { + string? parent = Path.GetDirectoryName(expectedPath); + if (parent != null) + { + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + } + + System.IO.File.WriteAllText(expectedPath, got); + } + else + { + if (!System.IO.File.Exists(expectedPath)) + { + throw new System.IO.FileNotFoundException( + "The file with the recorded trace does not " + + $"exist: {expectedPath}; maybe you want to set the environment " + + $"variable {Our.Tests.Common.RecordModeEnvironmentVariableName}?"); + } + + string expected = System.IO.File.ReadAllText(expectedPath); + Assert.AreEqual( + expected.Replace("\r\n", "\n"), + got.Replace("\r\n", "\n"), + $"The expected trace from {expectedPath} does not match the actual one"); + } + } + + [Test] + public void Test_Descend_of_SomeItem() + { + Our.SomeItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "Descend", + "SomeItem", + "maximal.json.trace")); + } // public void Test_Descend_of_SomeItem + + [Test] + public void Test_Descend_against_VisitorThrough_for_SomeItem() + { + Our.SomeItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem()); + + AssertDescendAndVisitorThroughSame( + instance); + } // public void Test_Descend_against_VisitorThrough_for_SomeItem + + [Test] + public void Test_Descend_of_SpecialItem() + { + Our.SpecialItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "Descend", + "SpecialItem", + "maximal.json.trace")); + } // public void Test_Descend_of_SpecialItem + + [Test] + public void Test_Descend_against_VisitorThrough_for_SpecialItem() + { + Our.SpecialItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem()); + + AssertDescendAndVisitorThroughSame( + instance); + } // public void Test_Descend_against_VisitorThrough_for_SpecialItem + + [Test] + public void Test_Descend_of_Container() + { + Our.Container instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "Descend", + "Container", + "maximal.json.trace")); + } // public void Test_Descend_of_Container + + [Test] + public void Test_Descend_against_VisitorThrough_for_Container() + { + Our.Container instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer()); + + AssertDescendAndVisitorThroughSame( + instance); + } // public void Test_Descend_against_VisitorThrough_for_Container + + [Test] + public void Test_Descend_of_Wrapper() + { + Our.Wrapper instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "Descend", + "Wrapper", + "maximal.json.trace")); + } // public void Test_Descend_of_Wrapper + + [Test] + public void Test_Descend_against_VisitorThrough_for_Wrapper() + { + Our.Wrapper instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper()); + + AssertDescendAndVisitorThroughSame( + instance); + } // public void Test_Descend_against_VisitorThrough_for_Wrapper + } // class TestDescendAndVisitorThrough +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendOnce.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendOnce.cs new file mode 100644 index 000000000..18516ee68 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestDescendOnce.cs @@ -0,0 +1,125 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Directory = System.IO.Directory; +using Path = System.IO.Path; + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestDescendOnce + { + private static void CompareOrRerecordTrace( + IClass instance, + string expectedPath) + { + var writer = new System.IO.StringWriter(); + foreach (var descendant in instance.DescendOnce()) + { + writer.WriteLine(Our.Tests.Common.Trace(descendant)); + } + + string got = writer.ToString(); + + if (Our.Tests.Common.RecordMode) + { + string? parent = Path.GetDirectoryName(expectedPath); + if (parent != null) + { + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + } + + System.IO.File.WriteAllText(expectedPath, got); + } + else + { + if (!System.IO.File.Exists(expectedPath)) + { + throw new System.IO.FileNotFoundException( + $"The file with the recorded trace does not exist: {expectedPath}; " + + "maybe you want to set the environment " + + $"variable {Our.Tests.Common.RecordModeEnvironmentVariableName}?"); + } + + string expected = System.IO.File.ReadAllText(expectedPath); + Assert.AreEqual( + expected.Replace("\r\n", "\n"), + got.Replace("\r\n", "\n"), + $"The expected trace from {expectedPath} does not match the actual one"); + } + } + + [Test] + public void Test_SomeItem() + { + Our.SomeItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "DescendOnce", + "SomeItem", + "maximal.json.trace")); + } // public void Test_SomeItem + + [Test] + public void Test_SpecialItem() + { + Our.SpecialItem instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "DescendOnce", + "SpecialItem", + "maximal.json.trace")); + } // public void Test_SpecialItem + + [Test] + public void Test_Container() + { + Our.Container instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "DescendOnce", + "Container", + "maximal.json.trace")); + } // public void Test_Container + + [Test] + public void Test_Wrapper() + { + Our.Wrapper instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper()); + + CompareOrRerecordTrace( + instance, + Path.Combine( + Our.Tests.Common.TestDataDir, + "DescendOnce", + "Wrapper", + "maximal.json.trace")); + } // public void Test_Wrapper + } // class TestDescendOnce +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestEnhancing.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestEnhancing.cs new file mode 100644 index 000000000..e1cea4910 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestEnhancing.cs @@ -0,0 +1,168 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using OurEnhancing = dummy.Enhancing; // renamed + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestEnhancing + { + class Enhancement + { + public readonly long SomeCustomId; + + public Enhancement(long someCustomId) + { + SomeCustomId = someCustomId; + } + } + + private static OurEnhancing.Enhancer CreateEnhancer() + { + long lastCustomId = 0; + + var enhancementFactory = new System.Func( + delegate + { + lastCustomId++; + return new Enhancement(lastCustomId); + } + ); + + return new OurEnhancing.Enhancer(enhancementFactory); + } + + [Test] + public void Test_SomeItem() + { + var instance = ( + Our.Tests.CommonJsonization.LoadMaximalSomeItem() + ); + + var enhancer = CreateEnhancer(); + + Assert.IsNull(enhancer.Unwrap(instance)); + + var wrapped = enhancer.Wrap(instance); + Assert.IsNotNull(wrapped); + + var idSet = new HashSet(); + + idSet.Add(enhancer.MustUnwrap(wrapped).SomeCustomId); + idSet.UnionWith( + wrapped + .Descend() + .Select( + (descendant) => + enhancer.MustUnwrap(descendant).SomeCustomId + ) + ); + + Assert.AreEqual(1, idSet.Min()); + Assert.AreEqual(idSet.Count, idSet.Max()); + } // public void Test_SomeItem + + [Test] + public void Test_SpecialItem() + { + var instance = ( + Our.Tests.CommonJsonization.LoadMaximalSpecialItem() + ); + + var enhancer = CreateEnhancer(); + + Assert.IsNull(enhancer.Unwrap(instance)); + + var wrapped = enhancer.Wrap(instance); + Assert.IsNotNull(wrapped); + + var idSet = new HashSet(); + + idSet.Add(enhancer.MustUnwrap(wrapped).SomeCustomId); + idSet.UnionWith( + wrapped + .Descend() + .Select( + (descendant) => + enhancer.MustUnwrap(descendant).SomeCustomId + ) + ); + + Assert.AreEqual(1, idSet.Min()); + Assert.AreEqual(idSet.Count, idSet.Max()); + } // public void Test_SpecialItem + + [Test] + public void Test_Container() + { + var instance = ( + Our.Tests.CommonJsonization.LoadMaximalContainer() + ); + + var enhancer = CreateEnhancer(); + + Assert.IsNull(enhancer.Unwrap(instance)); + + var wrapped = enhancer.Wrap(instance); + Assert.IsNotNull(wrapped); + + var idSet = new HashSet(); + + idSet.Add(enhancer.MustUnwrap(wrapped).SomeCustomId); + idSet.UnionWith( + wrapped + .Descend() + .Select( + (descendant) => + enhancer.MustUnwrap(descendant).SomeCustomId + ) + ); + + Assert.AreEqual(1, idSet.Min()); + Assert.AreEqual(idSet.Count, idSet.Max()); + } // public void Test_Container + + [Test] + public void Test_Wrapper() + { + var instance = ( + Our.Tests.CommonJsonization.LoadMaximalWrapper() + ); + + var enhancer = CreateEnhancer(); + + Assert.IsNull(enhancer.Unwrap(instance)); + + var wrapped = enhancer.Wrap(instance); + Assert.IsNotNull(wrapped); + + var idSet = new HashSet(); + + idSet.Add(enhancer.MustUnwrap(wrapped).SomeCustomId); + idSet.UnionWith( + wrapped + .Descend() + .Select( + (descendant) => + enhancer.MustUnwrap(descendant).SomeCustomId + ) + ); + + Assert.AreEqual(1, idSet.Min()); + Assert.AreEqual(idSet.Count, idSet.Max()); + } // public void Test_Wrapper + } // class TestEnhancing +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfConcreteClasses.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfConcreteClasses.cs new file mode 100644 index 000000000..bb17a7bfe --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfConcreteClasses.cs @@ -0,0 +1,798 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Directory = System.IO.Directory; +using Nodes = System.Text.Json.Nodes; +using Path = System.IO.Path; + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestJsonizationOfConcreteClasses + { + private static void AssertSerializeDeserializeEqualsOriginal( + Nodes.JsonNode originalNode, Our.IClass instance, string path) + { + Nodes.JsonObject? serialized = null; + try + { + serialized = Our.Jsonization.Serialize.ToJsonObject(instance); + } + catch (System.Exception exception) + { + Assert.Fail( + "Expected no exception upon serialization of an instance " + + $"de-serialized from {path}, but got: {exception}" + ); + } + + if (serialized == null) + { + Assert.Fail( + $"Unexpected null serialization of an instance from {path}" + ); + } + else + { + Our.Tests.CommonJson.CheckJsonNodesEqual( + originalNode, + serialized, + out Reporting.Error? inequalityError); + if (inequalityError != null) + { + Assert.Fail( + $"The original JSON from {path} is unequal the serialized JSON: " + + $"{Reporting.GenerateJsonPath(inequalityError.PathSegments)}: " + + inequalityError.Cause + ); + } + } + } + + private static void AssertEqualsExpectedOrRerecordDeserializationException( + Our.Jsonization.Exception? exception, + string path) + { + if (exception == null) + { + Assert.Fail( + $"Expected a Jsonization exception when de-serializing {path}, but got none." + ); + } + else + { + string exceptionPath = path + ".exception"; + string got = exception.Message; + if (Our.Tests.Common.RecordMode) + { + System.IO.File.WriteAllText(exceptionPath, got); + } + else + { + if (!System.IO.File.Exists(exceptionPath)) + { + throw new System.IO.FileNotFoundException( + $"The file with the recorded exception does not exist: {exceptionPath}; " + + "maybe you want to set the environment " + + $"variable {Our.Tests.Common.RecordModeEnvironmentVariableName}?"); + } + + string expected = System.IO.File.ReadAllText(exceptionPath); + Assert.AreEqual( + expected.Replace("\r\n", "\n"), + got.Replace("\r\n", "\n"), + $"The expected exception does not match the actual one for the file {path}"); + } + } + } + + [Test] + public void Test_SomeItem_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SomeItem" + ), + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SomeItemFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + node, instance, path); + } + } // public void Test_SomeItem_ok + + [Test] + public void Test_SomeItem_deserialization_from_non_object_fail() + { + var node = Nodes.JsonValue.Create("INVALID") + ?? throw new System.InvalidOperationException( + "Unexpected failure of the node creation"); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.SomeItemFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + if (exception == null) + { + throw new AssertionException("Expected an exception, but got none"); + } + + if ( + !exception.Message.StartsWith( + "Expected a JsonObject representing SomeItem, but got ")) + { + throw new AssertionException( + $"Unexpected exception message: {exception.Message}"); + } + } // public void Test_SomeItem_deserialization_from_non_object_fail + + [Test] + public void Test_SomeItem_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "SomeItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SomeItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.SomeItemFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_SomeItem_deserialization_fail + + [Test] + public void Test_SomeItem_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "SomeItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SomeItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SomeItemFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_SomeItem_verification_fail + + [Test] + public void Test_SpecialItem_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "SpecialItem" + ), + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + node, instance, path); + } + } // public void Test_SpecialItem_ok + + [Test] + public void Test_SpecialItem_deserialization_from_non_object_fail() + { + var node = Nodes.JsonValue.Create("INVALID") + ?? throw new System.InvalidOperationException( + "Unexpected failure of the node creation"); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + if (exception == null) + { + throw new AssertionException("Expected an exception, but got none"); + } + + if ( + !exception.Message.StartsWith( + "Expected a JsonObject representing SpecialItem, but got ")) + { + throw new AssertionException( + $"Unexpected exception message: {exception.Message}"); + } + } // public void Test_SpecialItem_deserialization_from_non_object_fail + + [Test] + public void Test_SpecialItem_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "SpecialItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SpecialItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_SpecialItem_deserialization_fail + + [Test] + public void Test_SpecialItem_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "SpecialItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SpecialItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_SpecialItem_verification_fail + + [Test] + public void Test_Container_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Container" + ), + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.ContainerFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + node, instance, path); + } + } // public void Test_Container_ok + + [Test] + public void Test_Container_deserialization_from_non_object_fail() + { + var node = Nodes.JsonValue.Create("INVALID") + ?? throw new System.InvalidOperationException( + "Unexpected failure of the node creation"); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.ContainerFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + if (exception == null) + { + throw new AssertionException("Expected an exception, but got none"); + } + + if ( + !exception.Message.StartsWith( + "Expected a JsonObject representing Container, but got ")) + { + throw new AssertionException( + $"Unexpected exception message: {exception.Message}"); + } + } // public void Test_Container_deserialization_from_non_object_fail + + [Test] + public void Test_Container_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "Container" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Container for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.ContainerFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_Container_deserialization_fail + + [Test] + public void Test_Container_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "Container" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Container for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.ContainerFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_Container_verification_fail + + [Test] + public void Test_Wrapper_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + "Wrapper" + ), + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.WrapperFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + node, instance, path); + } + } // public void Test_Wrapper_ok + + [Test] + public void Test_Wrapper_deserialization_from_non_object_fail() + { + var node = Nodes.JsonValue.Create("INVALID") + ?? throw new System.InvalidOperationException( + "Unexpected failure of the node creation"); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.WrapperFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + if (exception == null) + { + throw new AssertionException("Expected an exception, but got none"); + } + + if ( + !exception.Message.StartsWith( + "Expected a JsonObject representing Wrapper, but got ")) + { + throw new AssertionException( + $"Unexpected exception message: {exception.Message}"); + } + } // public void Test_Wrapper_deserialization_from_non_object_fail + + [Test] + public void Test_Wrapper_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "Wrapper" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Wrapper for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + Our.Jsonization.Exception? exception = null; + try + { + var _ = Our.Jsonization.Deserialize.WrapperFrom( + node); + } + catch (Our.Jsonization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_Wrapper_deserialization_fail + + [Test] + public void Test_Wrapper_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "Wrapper" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Wrapper for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + var node = Our.Tests.CommonJson.ReadFromFile(path); + + var instance = Our.Jsonization.Deserialize.WrapperFrom( + node); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_Wrapper_verification_fail + + /// + /// Read the first recorded example of the . + /// + private static Nodes.JsonNode LoadTheFirstExpected(string modelType) + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Json", + "Expected", + modelType), + "*.json", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + Assert.IsNotEmpty( + paths, + $"Expected at least one recorded example of {modelType}, but got none"); + + return Our.Tests.CommonJson.ReadFromFile(paths[0]); + } + + [Test] + public void TestSpecialItemLevelSerializationOutOfRange() + { + foreach (var value in new long[] { 9007199254740992L, -9007199254740992L }) + { + var node = LoadTheFirstExpected( + "SpecialItem"); + + var instance = Our.Jsonization.Deserialize.SpecialItemFrom( + node); + + instance.Level = value; + + Our.Jsonization.SerializationException? exception = null; + try + { + var _ = Our.Jsonization.Serialize.ToJsonObject(instance); + } + catch (Our.Jsonization.SerializationException observedException) + { + exception = observedException; + } + + Assert.IsNotNull( + exception, + "Expected the serialization to fail at " + + "Level" + + ", but it succeeded"); + + Assert.AreEqual( + "Level", + exception!.Path); + } + } // public void TestSpecialItemLevelSerializationOutOfRange + } // class TestJsonizationOfConcreteClasses +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfEnums.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfEnums.cs new file mode 100644 index 000000000..cd84ef87a --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfEnums.cs @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Nodes = System.Text.Json.Nodes; + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestJsonizationOfEnums + { + + } // class TestJsonizationOfEnums +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfInterfaces.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfInterfaces.cs new file mode 100644 index 000000000..59ac7f0c3 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestJsonizationOfInterfaces.cs @@ -0,0 +1,135 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestJsonizationOfInterfaces + { + [Test] + public void Test_round_trip_IItem_from_SomeItem() + { + var instance = Our.Tests.CommonJsonization.LoadMaximalSomeItem(); + + var jsonObject = Our.Jsonization.Serialize.ToJsonObject(instance); + + var anotherInstance = Our.Jsonization.Deserialize.IItemFrom( + jsonObject); + + var anotherJsonObject = Our.Jsonization.Serialize.ToJsonObject( + anotherInstance); + + Our.Tests.CommonJson.CheckJsonNodesEqual( + jsonObject, + anotherJsonObject, + out Our.Reporting.Error? error); + + if (error != null) + { + Assert.Fail( + "When we serialize the complete instance of SomeItem " + + "as IItem, we get an error in the round trip: " + + $"{Reporting.GenerateJsonPath(error.PathSegments)}: " + + error.Cause + ); + } + } // void Test_round_trip_IItem_from_SomeItem + + [Test] + public void Test_round_trip_IItem_from_SpecialItem() + { + var instance = Our.Tests.CommonJsonization.LoadMaximalSpecialItem(); + + var jsonObject = Our.Jsonization.Serialize.ToJsonObject(instance); + + var anotherInstance = Our.Jsonization.Deserialize.IItemFrom( + jsonObject); + + var anotherJsonObject = Our.Jsonization.Serialize.ToJsonObject( + anotherInstance); + + Our.Tests.CommonJson.CheckJsonNodesEqual( + jsonObject, + anotherJsonObject, + out Our.Reporting.Error? error); + + if (error != null) + { + Assert.Fail( + "When we serialize the complete instance of SpecialItem " + + "as IItem, we get an error in the round trip: " + + $"{Reporting.GenerateJsonPath(error.PathSegments)}: " + + error.Cause + ); + } + } // void Test_round_trip_IItem_from_SpecialItem + + [Test] + public void Test_round_trip_ISomeItem_from_SpecialItem() + { + var instance = Our.Tests.CommonJsonization.LoadMaximalSpecialItem(); + + var jsonObject = Our.Jsonization.Serialize.ToJsonObject(instance); + + var anotherInstance = Our.Jsonization.Deserialize.ISomeItemFrom( + jsonObject); + + var anotherJsonObject = Our.Jsonization.Serialize.ToJsonObject( + anotherInstance); + + Our.Tests.CommonJson.CheckJsonNodesEqual( + jsonObject, + anotherJsonObject, + out Our.Reporting.Error? error); + + if (error != null) + { + Assert.Fail( + "When we serialize the complete instance of SpecialItem " + + "as ISomeItem, we get an error in the round trip: " + + $"{Reporting.GenerateJsonPath(error.PathSegments)}: " + + error.Cause + ); + } + } // void Test_round_trip_ISomeItem_from_SpecialItem + + [Test] + public void Test_round_trip_ISomeItem_from_SomeItem() + { + var instance = Our.Tests.CommonJsonization.LoadMaximalSomeItem(); + + var jsonObject = Our.Jsonization.Serialize.ToJsonObject(instance); + + var anotherInstance = Our.Jsonization.Deserialize.ISomeItemFrom( + jsonObject); + + var anotherJsonObject = Our.Jsonization.Serialize.ToJsonObject( + anotherInstance); + + Our.Tests.CommonJson.CheckJsonNodesEqual( + jsonObject, + anotherJsonObject, + out Our.Reporting.Error? error); + + if (error != null) + { + Assert.Fail( + "When we serialize the complete instance of SomeItem " + + "as ISomeItem, we get an error in the round trip: " + + $"{Reporting.GenerateJsonPath(error.PathSegments)}: " + + error.Cause + ); + } + } // void Test_round_trip_ISomeItem_from_SomeItem + } // class TestJsonizationOfInterfaces +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestOverXOrEmpty.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestOverXOrEmpty.cs new file mode 100644 index 000000000..f3a9ee376 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestOverXOrEmpty.cs @@ -0,0 +1,40 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestOverXOrEmpty + { + [Test] + public void Test_Container_OverSpecialItemsOrEmpty() + { + foreach (Our.Container instance in new[] + { + Our.Tests.CommonJsonization.LoadMinimalContainer(), + Our.Tests.CommonJsonization.LoadMaximalContainer() + }) + { + int count = 0; + foreach (var _ in instance.OverSpecialItemsOrEmpty()) + { + count++; + } + + Assert.AreEqual( + instance.SpecialItems?.Count ?? 0, + count); + } + } // public void Test_Container_OverSpecialItemsOrEmpty + } // class TestOverXOrEmpty +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestStringHelpers.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestStringHelpers.cs new file mode 100644 index 000000000..45205caac --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestStringHelpers.cs @@ -0,0 +1,411 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + /// + /// Test len, the slicing of strings and find as used in + /// the transpiled code. + /// + /// + /// The transpiled code follows the Python implementation, since Python is + /// the language of the meta-model specifications. Hence, the lengths and + /// the positions count the characters (code points). The expected values have + /// been computed with Python, and all the SDKs test against the very same + /// cases. + /// + public class TestStringHelpers + { + [Test, Description("len('') gives 0: empty text")] + public void Test_len_empty_text() + { + Assert.AreEqual( + 0, + Our.Common.StringHelpers.Len( + "")); + } // void Test_len_empty_text + + [Test, Description("len('abc') gives 3: ASCII text")] + public void Test_len_ASCII_text() + { + Assert.AreEqual( + 3, + Our.Common.StringHelpers.Len( + "abc")); + } // void Test_len_ASCII_text + + [Test, Description("len('é-') gives 2: character of two UTF-8 bytes")] + public void Test_len_character_of_two_UTF_8_bytes() + { + Assert.AreEqual( + 2, + Our.Common.StringHelpers.Len( + "é-")); + } // void Test_len_character_of_two_UTF_8_bytes + + [Test, Description("len('😀-') gives 2: character beyond the Basic Multilingual Plane")] + public void Test_len_character_beyond_the_Basic_Multilingual_Plane() + { + Assert.AreEqual( + 2, + Our.Common.StringHelpers.Len( + "😀-")); + } // void Test_len_character_beyond_the_Basic_Multilingual_Plane + + [Test, Description("len('aé😀b😀') gives 5: mixed characters")] + public void Test_len_mixed_characters() + { + Assert.AreEqual( + 5, + Our.Common.StringHelpers.Len( + "aé😀b😀")); + } // void Test_len_mixed_characters + + [Test, Description("'abcde'[1:3] gives 'bc': start and end")] + public void Test_slice_start_and_end() + { + Assert.AreEqual( + "bc", + Our.Common.StringHelpers.Slice("abcde", 1, 3)); + } // void Test_slice_start_and_end + + [Test, Description("'abcde'[:2] gives 'ab': no start")] + public void Test_slice_no_start() + { + Assert.AreEqual( + "ab", + Our.Common.StringHelpers.Slice("abcde", 0, 2)); + } // void Test_slice_no_start + + [Test, Description("'abcde'[3:] gives 'de': no end")] + public void Test_slice_no_end() + { + Assert.AreEqual( + "de", + Our.Common.StringHelpers.Slice("abcde", 3)); + } // void Test_slice_no_end + + [Test, Description("'abcde'[:] gives 'abcde': neither start nor end")] + public void Test_slice_neither_start_nor_end() + { + Assert.AreEqual( + "abcde", + Our.Common.StringHelpers.Slice("abcde", 0)); + } // void Test_slice_neither_start_nor_end + + [Test, Description("'abcde'[-2:] gives 'de': negative start counts from the end")] + public void Test_slice_negative_start_counts_from_the_end() + { + Assert.AreEqual( + "de", + Our.Common.StringHelpers.Slice("abcde", -2)); + } // void Test_slice_negative_start_counts_from_the_end + + [Test, Description("'abcde'[:-2] gives 'abc': negative end counts from the end")] + public void Test_slice_negative_end_counts_from_the_end() + { + Assert.AreEqual( + "abc", + Our.Common.StringHelpers.Slice("abcde", 0, -2)); + } // void Test_slice_negative_end_counts_from_the_end + + [Test, Description("'abcde'[-4:-1] gives 'bcd': negative start and end")] + public void Test_slice_negative_start_and_end() + { + Assert.AreEqual( + "bcd", + Our.Common.StringHelpers.Slice("abcde", -4, -1)); + } // void Test_slice_negative_start_and_end + + [Test, Description("'abcde'[-10:2] gives 'ab': negative start before the beginning is clamped")] + public void Test_slice_negative_start_before_the_beginning_is_clamped() + { + Assert.AreEqual( + "ab", + Our.Common.StringHelpers.Slice("abcde", -10, 2)); + } // void Test_slice_negative_start_before_the_beginning_is_clamped + + [Test, Description("'abcde'[2:10] gives 'cde': end beyond the end is clamped")] + public void Test_slice_end_beyond_the_end_is_clamped() + { + Assert.AreEqual( + "cde", + Our.Common.StringHelpers.Slice("abcde", 2, 10)); + } // void Test_slice_end_beyond_the_end_is_clamped + + [Test, Description("'abcde'[10:] gives '': start beyond the end gives empty")] + public void Test_slice_start_beyond_the_end_gives_empty() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("abcde", 10)); + } // void Test_slice_start_beyond_the_end_gives_empty + + [Test, Description("'abcde'[3:1] gives '': start after end gives empty")] + public void Test_slice_start_after_end_gives_empty() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("abcde", 3, 1)); + } // void Test_slice_start_after_end_gives_empty + + [Test, Description("'abcde'[-1:-3] gives '': negative start after negative end gives empty")] + public void Test_slice_negative_start_after_negative_end_gives_empty() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("abcde", -1, -3)); + } // void Test_slice_negative_start_after_negative_end_gives_empty + + [Test, Description("'abcde'[5:5] gives '': start equal to end gives empty")] + public void Test_slice_start_equal_to_end_gives_empty() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("abcde", 5, 5)); + } // void Test_slice_start_equal_to_end_gives_empty + + [Test, Description("''[0:0] gives '': empty text")] + public void Test_slice_empty_text() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("", 0, 0)); + } // void Test_slice_empty_text + + [Test, Description("''[-1:] gives '': negative start on empty text")] + public void Test_slice_negative_start_on_empty_text() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("", -1)); + } // void Test_slice_negative_start_on_empty_text + + [Test, Description("''[:5] gives '': end beyond the end of empty text")] + public void Test_slice_end_beyond_the_end_of_empty_text() + { + Assert.AreEqual( + "", + Our.Common.StringHelpers.Slice("", 0, 5)); + } // void Test_slice_end_beyond_the_end_of_empty_text + + [Test, Description("'é-x'[1:] gives '-x': after a character of two UTF-8 bytes")] + public void Test_slice_after_a_character_of_two_UTF_8_bytes() + { + Assert.AreEqual( + "-x", + Our.Common.StringHelpers.Slice("é-x", 1)); + } // void Test_slice_after_a_character_of_two_UTF_8_bytes + + [Test, Description("'aéb'[1:2] gives 'é': character of two UTF-8 bytes")] + public void Test_slice_character_of_two_UTF_8_bytes() + { + Assert.AreEqual( + "é", + Our.Common.StringHelpers.Slice("aéb", 1, 2)); + } // void Test_slice_character_of_two_UTF_8_bytes + + [Test, Description("'a😀b'[1:2] gives '😀': character beyond the Basic Multilingual Plane")] + public void Test_slice_character_beyond_the_Basic_Multilingual_Plane() + { + Assert.AreEqual( + "😀", + Our.Common.StringHelpers.Slice("a😀b", 1, 2)); + } // void Test_slice_character_beyond_the_Basic_Multilingual_Plane + + [Test, Description("'😀-x'[1:] gives '-x': after a character beyond the Basic Multilingual Plane")] + public void Test_slice_after_a_character_beyond_the_Basic_Multilingual_Plane() + { + Assert.AreEqual( + "-x", + Our.Common.StringHelpers.Slice("😀-x", 1)); + } // void Test_slice_after_a_character_beyond_the_Basic_Multilingual_Plane + + [Test, Description("'aé😀b😀'[-3:-1] gives '😀b': negative start on characters beyond ASCII")] + public void Test_slice_negative_start_on_characters_beyond_ASCII() + { + Assert.AreEqual( + "😀b", + Our.Common.StringHelpers.Slice("aé😀b😀", -3, -1)); + } // void Test_slice_negative_start_on_characters_beyond_ASCII + + [Test, Description("'abcabc'.find('b') gives 1: found")] + public void Test_find_found() + { + Assert.AreEqual( + 1, + Our.Common.StringHelpers.Find("abcabc", "b")); + } // void Test_find_found + + [Test, Description("'abcabc'.find('x') gives -1: not found")] + public void Test_find_not_found() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("abcabc", "x")); + } // void Test_find_not_found + + [Test, Description("'abcabc'.find('') gives 0: empty sub")] + public void Test_find_empty_sub() + { + Assert.AreEqual( + 0, + Our.Common.StringHelpers.Find("abcabc", "")); + } // void Test_find_empty_sub + + [Test, Description("'ab'.find('abc') gives -1: sub longer than text")] + public void Test_find_sub_longer_than_text() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("ab", "abc")); + } // void Test_find_sub_longer_than_text + + [Test, Description("'abcabc'.find('b', 2) gives 4: found after start")] + public void Test_find_found_after_start() + { + Assert.AreEqual( + 4, + Our.Common.StringHelpers.Find("abcabc", "b", 2)); + } // void Test_find_found_after_start + + [Test, Description("'abcabc'.find('b', 5) gives -1: found only before start")] + public void Test_find_found_only_before_start() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("abcabc", "b", 5)); + } // void Test_find_found_only_before_start + + [Test, Description("'abcabc'.find('c', -1) gives 5: negative start counts from the end")] + public void Test_find_negative_start_counts_from_the_end() + { + Assert.AreEqual( + 5, + Our.Common.StringHelpers.Find("abcabc", "c", -1)); + } // void Test_find_negative_start_counts_from_the_end + + [Test, Description("'abcabc'.find('a', -3) gives 3: negative start finds the later occurrence")] + public void Test_find_negative_start_finds_the_later_occurrence() + { + Assert.AreEqual( + 3, + Our.Common.StringHelpers.Find("abcabc", "a", -3)); + } // void Test_find_negative_start_finds_the_later_occurrence + + [Test, Description("'abcabc'.find('a', -10) gives 0: negative start before the beginning is clamped")] + public void Test_find_negative_start_before_the_beginning_is_clamped() + { + Assert.AreEqual( + 0, + Our.Common.StringHelpers.Find("abcabc", "a", -10)); + } // void Test_find_negative_start_before_the_beginning_is_clamped + + [Test, Description("'abcabc'.find('', 6) gives 6: empty sub at the end")] + public void Test_find_empty_sub_at_the_end() + { + Assert.AreEqual( + 6, + Our.Common.StringHelpers.Find("abcabc", "", 6)); + } // void Test_find_empty_sub_at_the_end + + [Test, Description("'abcabc'.find('', 7) gives -1: empty sub beyond the end gives -1")] + public void Test_find_empty_sub_beyond_the_end_gives_1() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("abcabc", "", 7)); + } // void Test_find_empty_sub_beyond_the_end_gives_1 + + [Test, Description("'abcabc'.find('c', 10) gives -1: start beyond the end gives -1")] + public void Test_find_start_beyond_the_end_gives_1() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("abcabc", "c", 10)); + } // void Test_find_start_beyond_the_end_gives_1 + + [Test, Description("''.find('') gives 0: empty sub in empty text")] + public void Test_find_empty_sub_in_empty_text() + { + Assert.AreEqual( + 0, + Our.Common.StringHelpers.Find("", "")); + } // void Test_find_empty_sub_in_empty_text + + [Test, Description("''.find('', 1) gives -1: empty sub beyond the end of empty text gives -1")] + public void Test_find_empty_sub_beyond_the_end_of_empty_text_gives_1() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("", "", 1)); + } // void Test_find_empty_sub_beyond_the_end_of_empty_text_gives_1 + + [Test, Description("''.find('x') gives -1: not found in empty text")] + public void Test_find_not_found_in_empty_text() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("", "x")); + } // void Test_find_not_found_in_empty_text + + [Test, Description("'é-'.find('-') gives 1: after a character of two UTF-8 bytes")] + public void Test_find_after_a_character_of_two_UTF_8_bytes() + { + Assert.AreEqual( + 1, + Our.Common.StringHelpers.Find("é-", "-")); + } // void Test_find_after_a_character_of_two_UTF_8_bytes + + [Test, Description("'😀-'.find('-') gives 1: after a character beyond the Basic Multilingual Plane")] + public void Test_find_after_a_character_beyond_the_Basic_Multilingual_Plane() + { + Assert.AreEqual( + 1, + Our.Common.StringHelpers.Find("😀-", "-")); + } // void Test_find_after_a_character_beyond_the_Basic_Multilingual_Plane + + [Test, Description("'a😀b'.find('😀') gives 1: character beyond the Basic Multilingual Plane")] + public void Test_find_character_beyond_the_Basic_Multilingual_Plane() + { + Assert.AreEqual( + 1, + Our.Common.StringHelpers.Find("a😀b", "😀")); + } // void Test_find_character_beyond_the_Basic_Multilingual_Plane + + [Test, Description("'😀a😀a'.find('a', 2) gives 3: start after a character beyond ASCII")] + public void Test_find_start_after_a_character_beyond_ASCII() + { + Assert.AreEqual( + 3, + Our.Common.StringHelpers.Find("😀a😀a", "a", 2)); + } // void Test_find_start_after_a_character_beyond_ASCII + + [Test, Description("'😀a😀a'.find('😀', -2) gives 2: negative start on characters beyond ASCII")] + public void Test_find_negative_start_on_characters_beyond_ASCII() + { + Assert.AreEqual( + 2, + Our.Common.StringHelpers.Find("😀a😀a", "😀", -2)); + } // void Test_find_negative_start_on_characters_beyond_ASCII + + [Test, Description("'é😀'.find('', 3) gives -1: start beyond the end of characters beyond ASCII")] + public void Test_find_start_beyond_the_end_of_characters_beyond_ASCII() + { + Assert.AreEqual( + -1, + Our.Common.StringHelpers.Find("é😀", "", 3)); + } // void Test_find_start_beyond_the_end_of_characters_beyond_ASCII + } // class TestStringHelpers +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestVerificationOfEnums.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestVerificationOfEnums.cs new file mode 100644 index 000000000..3b939cb27 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestVerificationOfEnums.cs @@ -0,0 +1,22 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using System.Linq; // can't alias +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestVerificationOfEnums + { + + } // class TestVerificationOfEnums +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXOrDefault.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXOrDefault.cs new file mode 100644 index 000000000..ec2dcc3de --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXOrDefault.cs @@ -0,0 +1,72 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Directory = System.IO.Directory; +using Nodes = System.Text.Json.Nodes; +using Path = System.IO.Path; + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestXOrDefault + { + private static void CompareOrRerecordValue( + object value, + string expectedPath) + { + Nodes.JsonNode got = Our.Tests.CommonJson.ToJson( + value); + + if (Our.Tests.Common.RecordMode) + { + string? parent = Path.GetDirectoryName(expectedPath); + if (parent != null) + { + if (!Directory.Exists(parent)) + { + Directory.CreateDirectory(parent); + } + } + + System.IO.File.WriteAllText( + expectedPath, got.ToJsonString()); + } + else + { + if (!System.IO.File.Exists(expectedPath)) + { + throw new System.IO.FileNotFoundException( + $"The file with the recorded value does not exist: {expectedPath}; " + + "maybe you want to set the environment " + + $"variable {Our.Tests.Common.RecordModeEnvironmentVariableName}?"); + } + + Nodes.JsonNode expected = Our.Tests.CommonJson.ReadFromFile( + expectedPath); + + Our.Tests.CommonJson.CheckJsonNodesEqual( + expected, got, out Our.Reporting.Error? error); + + if (error != null) + { + Assert.Fail( + $"The original value from {expectedPath} is unequal the obtain value " + + "when serialized to JSON: " + + $"{Reporting.GenerateJsonPath(error.PathSegments)}: " + + error.Cause + ); + } + } + } + } // class TestXOrDefault +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationErrors.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationErrors.cs new file mode 100644 index 000000000..411be3173 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationErrors.cs @@ -0,0 +1,156 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Path = System.IO.Path; + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestXmlizationErrors + { + [Test] + public void Test_error_on_unexpected_declaration() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "someItem", + "minimal.xml"); + + var text = System.IO.File.ReadAllText(path, System.Text.Encoding.UTF8); + + // We assume no XML declarations in our test data. + if (!text.StartsWith("" + + text + ); + + using var stringReader = new System.IO.StringReader( + text); + + using var xmlReader = System.Xml.XmlReader.Create( + stringReader); + + // We intentionally do not call `MoveToContent` to test the error message. + // This is a very common situation. + + string? message = null; + + try + { + Our.Xmlization.Deserialize.SomeItemFrom( + xmlReader); + } + catch (Our.Xmlization.Exception exception) + { + message = exception.Message; + } + + if (message == null) + { + throw new AssertionException("Unexpected no exception"); + } + Assert.AreEqual( + "Unexpected XML declaration when reading an instance of " + + "class SomeItem, as we expect the reader to be set at content " + + "with MoveToContent at: the beginning", + message); + } + + [Test] + public void Test_error_on_duplicate_property() + { + string path = Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "someItem", + "minimal.xml"); + + var text = System.IO.File.ReadAllText(path, System.Text.Encoding.UTF8); + + // We cut the element of the property out of the recorded example and put it + // in a second time, just before the closing tag of the instance. + int start = text.IndexOf( + "", + System.StringComparison.Ordinal); + + string property; + if (start >= 0) + { + int end = text.IndexOf( + "", + start, + System.StringComparison.Ordinal); + + property = text.Substring(start, end + 7 - start); + } + else + { + // The element is written self-closing in the example, an empty list being + // the usual reason. We write that very element out ourselves. + property = ""; + } + + int insertionIndex = text.LastIndexOf( + "", + System.StringComparison.Ordinal); + + if (insertionIndex < 0) + { + throw new System.InvalidOperationException( + "We expect the recorded example to contain the closing tag " + + $", but it does not: {path}"); + } + + string brokenText = ( + text.Substring(0, insertionIndex) + + property + + text.Substring(insertionIndex)); + + using var stringReader = new System.IO.StringReader( + brokenText); + + using var xmlReader = System.Xml.XmlReader.Create( + stringReader); + + string? message = null; + + try + { + Our.Xmlization.Deserialize.SomeItemFrom( + xmlReader); + } + catch (Our.Xmlization.Exception exception) + { + message = exception.Message; + } + + if (message == null) + { + throw new AssertionException( + "Expected an exception when the property " + + "name is given twice, but got none"); + } + } + } // class TestXmlizationErrors +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfConcreteClasses.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfConcreteClasses.cs new file mode 100644 index 000000000..26bc061f2 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfConcreteClasses.cs @@ -0,0 +1,703 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using Directory = System.IO.Directory; +using Path = System.IO.Path; + +using NUnit.Framework; // can't alias +using System.Linq; // can't alias +using System.Xml.Linq; // can't alias + +namespace dummy.Tests +{ + public class TestXmlizationOfConcreteClasses + { + private static void CheckElementsEqual( + XElement expected, + XElement got, + out Reporting.Error? error) + { + error = null; + + if (expected.Name.LocalName != got.Name.LocalName) + { + error = new Reporting.Error( + "Mismatch in element names: " + + $"{expected} != {got}" + ); + return; + } + + string? expectedContent = (expected.FirstNode as XText)?.Value; + string? gotContent = (got.FirstNode as XText)?.Value; + + if (expectedContent != gotContent) + { + error = new Reporting.Error( + $"Mismatch in element contents: {expected} != {got}" + ); + return; + } + + var expectedChildren = expected.Elements().ToList(); + var gotChildren = got.Elements().ToList(); + + if (expectedChildren.Count != gotChildren.Count) + { + error = new Reporting.Error( + $"Mismatch in child elements: {expected} != {got}" + ); + return; + } + + for (int i = 0; i < expectedChildren.Count; i++) + { + CheckElementsEqual( + expectedChildren[i], + gotChildren[i], + out error); + + if (error != null) + { + error.PrependSegment( + new Reporting.IndexSegment(i)); + + error.PrependSegment( + new Reporting.NameSegment( + expected.Name.ToString())); + } + } + } + + private static void AssertSerializeDeserializeEqualsOriginal( + Our.IClass instance, string path) + { + // Serialize + var outputBuilder = new System.Text.StringBuilder(); + + { + using var writer = System.Xml.XmlWriter.Create( + outputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + } + ); + Our.Xmlization.Serialize.To( + instance, + writer); + } + + string outputText = outputBuilder.ToString(); + + // Compare input == output + { + using var outputReader = new System.IO.StringReader(outputText); + var gotDoc = XDocument.Load(outputReader); + + Assert.AreEqual( + gotDoc.Root?.Name.Namespace.ToString(), + "https://dummy.com"); + + foreach (var child in gotDoc.Descendants()) + { + Assert.IsTrue( + child.GetDefaultNamespace().NamespaceName == "https://dummy.com", + $"Unexpected namespace of {child.Name}: " + + $"{child.GetDefaultNamespace().NamespaceName}"); + } + + var expectedDoc = XDocument.Load(path); + + CheckElementsEqual( + expectedDoc.Root!, + gotDoc.Root!, + out Reporting.Error? inequalityError); + + if (inequalityError != null) + { + Assert.Fail( + $"The original XML from {path} is unequal the serialized XML: " + + $"#/{Reporting.GenerateRelativeXPath(inequalityError.PathSegments)}: " + + inequalityError.Cause + ); + } + } + } + + private static void AssertEqualsExpectedOrRerecordDeserializationException( + Our.Xmlization.Exception? exception, + string path) + { + if (exception == null) + { + Assert.Fail( + $"Expected a Xmlization exception when de-serializing {path}, but got none." + ); + } + else + { + string exceptionPath = path + ".exception"; + string got = exception.Message; + if (Our.Tests.Common.RecordMode) + { + System.IO.File.WriteAllText(exceptionPath, got); + } + else + { + if (!System.IO.File.Exists(exceptionPath)) + { + throw new System.IO.FileNotFoundException( + "The file with the recorded exception does not " + + $"exist: {exceptionPath}; maybe you want to set the environment " + + $"variable {Our.Tests.Common.RecordModeEnvironmentVariableName}?"); + } + + string expected = System.IO.File.ReadAllText(exceptionPath); + Assert.AreEqual( + expected.Replace("\r\n", "\n"), + got.Replace("\r\n", "\n"), + $"The expected exception does not match the actual one for the file {path}"); + } + } + } + + [Test] + public void Test_SomeItem_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "someItem" + ), + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.SomeItemFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + instance, path); + } + } // public void Test_SomeItem_ok + + [Test] + public void Test_SomeItem_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "someItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SomeItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + Our.Xmlization.Exception? exception = null; + + try + { + _ = Our.Xmlization.Deserialize.SomeItemFrom( + xmlReader); + } + catch (Our.Xmlization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_SomeItem_deserialization_fail + + [Test] + public void Test_SomeItem_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "someItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SomeItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.SomeItemFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_SomeItem_verification_fail + + [Test] + public void Test_SpecialItem_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "specialItem" + ), + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.SpecialItemFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + instance, path); + } + } // public void Test_SpecialItem_ok + + [Test] + public void Test_SpecialItem_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "specialItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SpecialItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + Our.Xmlization.Exception? exception = null; + + try + { + _ = Our.Xmlization.Deserialize.SpecialItemFrom( + xmlReader); + } + catch (Our.Xmlization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_SpecialItem_deserialization_fail + + [Test] + public void Test_SpecialItem_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "specialItem" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of SpecialItem for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.SpecialItemFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_SpecialItem_verification_fail + + [Test] + public void Test_Container_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "container" + ), + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.ContainerFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + instance, path); + } + } // public void Test_Container_ok + + [Test] + public void Test_Container_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "container" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Container for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + Our.Xmlization.Exception? exception = null; + + try + { + _ = Our.Xmlization.Deserialize.ContainerFrom( + xmlReader); + } + catch (Our.Xmlization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_Container_deserialization_fail + + [Test] + public void Test_Container_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "container" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Container for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.ContainerFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_Container_verification_fail + + [Test] + public void Test_Wrapper_ok() + { + var paths = Directory.GetFiles( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Expected", + "wrapper" + ), + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.WrapperFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertNoVerificationErrors(errors, path); + + AssertSerializeDeserializeEqualsOriginal( + instance, path); + } + } // public void Test_Wrapper_ok + + [Test] + public void Test_Wrapper_deserialization_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "wrapper" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Wrapper for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + Our.Xmlization.Exception? exception = null; + + try + { + _ = Our.Xmlization.Deserialize.WrapperFrom( + xmlReader); + } + catch (Our.Xmlization.Exception observedException) + { + exception = observedException; + } + + AssertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void Test_Wrapper_deserialization_fail + + [Test] + public void Test_Wrapper_verification_fail() + { + foreach ( + string causeDir in + Directory.GetDirectories( + Path.Combine( + Our.Tests.Common.TestDataDir, + "Xml", + "Unexpected", + "Invalid" + ) + ) + ) + { + string clsDir = Path.Combine( + causeDir, + "wrapper" + ); + + if (!Directory.Exists(clsDir)) + { + // No examples of Wrapper for the failure cause. + continue; + } + + var paths = Directory.GetFiles( + clsDir, + "*.xml", + System.IO.SearchOption.AllDirectories).ToList(); + paths.Sort(); + + foreach (var path in paths) + { + using var xmlReader = System.Xml.XmlReader.Create(path); + xmlReader.MoveToContent(); + + var instance = Our.Xmlization.Deserialize.WrapperFrom( + xmlReader); + + var errors = Our.Verification.Verify(instance).ToList(); + Our.Tests.Common.AssertEqualsExpectedOrRerecordVerificationErrors( + errors, path); + } + } + } // public void Test_Wrapper_verification_fail + } // class TestXmlizationOfConcreteClasses +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfInterfaces.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfInterfaces.cs new file mode 100644 index 000000000..df712b72e --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy.Tests/TestXmlizationOfInterfaces.cs @@ -0,0 +1,251 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed + +using NUnit.Framework; // can't alias + +namespace dummy.Tests +{ + public class TestXmlizationOfInterfaces + { + [Test] + public void Test_round_trip_IItem_from_SomeItem() + { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + var instance = Our.Tests.CommonJsonization.LoadMaximalSomeItem(); + + // The round-trip starts here. + var outputBuilder = new System.Text.StringBuilder(); + + // Serialize to XML + { + using var xmlWriter = System.Xml.XmlWriter.Create( + outputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + instance, + xmlWriter); + } + + // De-serialize from XML + string outputText = outputBuilder.ToString(); + + using var outputReader = new System.IO.StringReader(outputText); + + using var xmlReader = System.Xml.XmlReader.Create( + outputReader, + new System.Xml.XmlReaderSettings()); + + var anotherInstance = Our.Xmlization.Deserialize.IItemFrom( + xmlReader); + + // Serialize back to XML + var anotherOutputBuilder = new System.Text.StringBuilder(); + + { + using var anotherXmlWriter = System.Xml.XmlWriter.Create( + anotherOutputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + anotherInstance, + anotherXmlWriter); + } + + // Compare + Assert.AreEqual(outputText, anotherOutputBuilder.ToString()); + } // void Test_round_trip_IItem_from_SomeItem + + [Test] + public void Test_round_trip_IItem_from_SpecialItem() + { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + var instance = Our.Tests.CommonJsonization.LoadMaximalSpecialItem(); + + // The round-trip starts here. + var outputBuilder = new System.Text.StringBuilder(); + + // Serialize to XML + { + using var xmlWriter = System.Xml.XmlWriter.Create( + outputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + instance, + xmlWriter); + } + + // De-serialize from XML + string outputText = outputBuilder.ToString(); + + using var outputReader = new System.IO.StringReader(outputText); + + using var xmlReader = System.Xml.XmlReader.Create( + outputReader, + new System.Xml.XmlReaderSettings()); + + var anotherInstance = Our.Xmlization.Deserialize.IItemFrom( + xmlReader); + + // Serialize back to XML + var anotherOutputBuilder = new System.Text.StringBuilder(); + + { + using var anotherXmlWriter = System.Xml.XmlWriter.Create( + anotherOutputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + anotherInstance, + anotherXmlWriter); + } + + // Compare + Assert.AreEqual(outputText, anotherOutputBuilder.ToString()); + } // void Test_round_trip_IItem_from_SpecialItem + + [Test] + public void Test_round_trip_ISomeItem_from_SpecialItem() + { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + var instance = Our.Tests.CommonJsonization.LoadMaximalSpecialItem(); + + // The round-trip starts here. + var outputBuilder = new System.Text.StringBuilder(); + + // Serialize to XML + { + using var xmlWriter = System.Xml.XmlWriter.Create( + outputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + instance, + xmlWriter); + } + + // De-serialize from XML + string outputText = outputBuilder.ToString(); + + using var outputReader = new System.IO.StringReader(outputText); + + using var xmlReader = System.Xml.XmlReader.Create( + outputReader, + new System.Xml.XmlReaderSettings()); + + var anotherInstance = Our.Xmlization.Deserialize.ISomeItemFrom( + xmlReader); + + // Serialize back to XML + var anotherOutputBuilder = new System.Text.StringBuilder(); + + { + using var anotherXmlWriter = System.Xml.XmlWriter.Create( + anotherOutputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + anotherInstance, + anotherXmlWriter); + } + + // Compare + Assert.AreEqual(outputText, anotherOutputBuilder.ToString()); + } // void Test_round_trip_ISomeItem_from_SpecialItem + + [Test] + public void Test_round_trip_ISomeItem_from_SomeItem() + { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + var instance = Our.Tests.CommonJsonization.LoadMaximalSomeItem(); + + // The round-trip starts here. + var outputBuilder = new System.Text.StringBuilder(); + + // Serialize to XML + { + using var xmlWriter = System.Xml.XmlWriter.Create( + outputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + instance, + xmlWriter); + } + + // De-serialize from XML + string outputText = outputBuilder.ToString(); + + using var outputReader = new System.IO.StringReader(outputText); + + using var xmlReader = System.Xml.XmlReader.Create( + outputReader, + new System.Xml.XmlReaderSettings()); + + var anotherInstance = Our.Xmlization.Deserialize.ISomeItemFrom( + xmlReader); + + // Serialize back to XML + var anotherOutputBuilder = new System.Text.StringBuilder(); + + { + using var anotherXmlWriter = System.Xml.XmlWriter.Create( + anotherOutputBuilder, + new System.Xml.XmlWriterSettings() + { + Encoding = System.Text.Encoding.UTF8, + OmitXmlDeclaration = true + }); + + Our.Xmlization.Serialize.To( + anotherInstance, + anotherXmlWriter); + } + + // Compare + Assert.AreEqual(outputText, anotherOutputBuilder.ToString()); + } // void Test_round_trip_ISomeItem_from_SomeItem + } // class TestXmlizationOfInterfaces +} // namespace dummy.Tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/common.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/common.cs new file mode 100644 index 000000000..0d82a8272 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/common.cs @@ -0,0 +1,199 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + + +namespace dummy +{ + /// + /// Provide the helpers shared by the transpiled code in the other modules. + /// + public static class Common + { + /// + /// Provide operations on tuples. + /// + public static class TupleHelpers + { + /// + /// Count the items of . + /// + public static int Len(System.Runtime.CompilerServices.ITuple tuple) + { + return tuple.Length; + } + } // public static class TupleHelpers + + /// + /// Provide string operations which follow the Python implementation, since + /// Python is the language of the meta-model specifications. + /// + /// + /// The lengths and the positions count the characters (code points), and not + /// the UTF-16 code units of the C# strings. Hence, a character beyond the Basic + /// Multilingual Plane counts as one, though it takes two UTF-16 code units + /// (a surrogate pair). + /// + public static class StringHelpers + { + /// + /// Check whether a surrogate pair starts at + /// in . + /// + private static bool IsSurrogatePairAt(string text, int offset) + { + return ( + offset + 1 < text.Length + && char.IsHighSurrogate(text[offset]) + && char.IsLowSurrogate(text[offset + 1]) + ); + } + + /// + /// Count the characters of between the UTF-16 + /// offsets and . + /// + private static int CountCharacters( + string text, + int startOffset, + int endOffset + ) + { + int count = 0; + int offset = startOffset; + while (offset < endOffset) + { + offset += IsSurrogatePairAt(text, offset) ? 2 : 1; + count++; + } + + return count; + } + + /// + /// Compute the UTF-16 offset of the character at + /// in . + /// + private static int OffsetOf(string text, int position) + { + int offset = 0; + for (int i = 0; i < position; i++) + { + offset += IsSurrogatePairAt(text, offset) ? 2 : 1; + } + + return offset; + } + + /// + /// Resolve in a string of + /// as Python does in slicing. + /// + /// + /// A negative position counts from the end, and the positions out of range + /// are clamped to the string. + /// + private static int ResolvePosition(long position, int length) + { + if (position < 0) + { + return (int)System.Math.Max(position + length, 0); + } + + return (int)System.Math.Min(position, length); + } + + /// + /// Count the characters (code points) of . + /// + /// + /// We follow the Python implementation of len, since Python is + /// the language of the meta-model specifications. Hence, a character beyond + /// the Basic Multilingual Plane counts as one, unlike in text.Length. + /// + public static int Len(string text) + { + return CountCharacters(text, 0, text.Length); + } + + /// + /// Slice from up to + /// , exclusive. + /// + /// + /// We follow the Python implementation of slicing, since Python is + /// the language of the meta-model specifications. Hence, the positions count + /// the characters (code points), a negative position counts from the end, + /// the positions out of range are clamped to the string, and the slice is + /// empty if is not before . + /// If is not given, we slice up to the end of + /// . + /// + public static string Slice(string text, long start, long? end = null) + { + int length = Len(text); + int theStart = ResolvePosition(start, length); + int theEnd = end is null + ? length + : ResolvePosition(end.Value, length); + + if (theStart >= theEnd) + { + return ""; + } + + int startOffset = OffsetOf(text, theStart); + int endOffset = OffsetOf(text, theEnd); + return text.Substring(startOffset, endOffset - startOffset); + } + + /// + /// Find the first in from + /// on. + /// + /// + /// We follow the Python implementation of str.find, since Python is + /// the language of the meta-model specifications. Hence, the positions count + /// the characters (code points), a negative counts + /// from the end, and a beyond the end of + /// gives -1. We compare the strings ordinally. + /// + /// + /// The position of in , + /// or -1 if not found + /// + public static long Find(string text, string sub, long start = 0) + { + int length = Len(text); + long theStart = start < 0 + ? System.Math.Max(start + length, 0) + : start; + + if (theStart > length) + { + return -1; + } + + int startOffset = OffsetOf(text, (int)theStart); + int offset = text.IndexOf( + sub, + startOffset, + System.StringComparison.Ordinal + ); + + if (offset == -1) + { + return -1; + } + + return theStart + CountCharacters(text, startOffset, offset); + } + } // public static class StringHelpers + } // public static class Common +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/constants.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/constants.cs new file mode 100644 index 000000000..e89d2649f --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/constants.cs @@ -0,0 +1,22 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Provide constant values of the meta-model. + /// + public static class Constants + { + + } // public static class Constants +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/copying.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/copying.cs new file mode 100644 index 000000000..30cfc4469 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/copying.cs @@ -0,0 +1,178 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Allow for making shallow and deep copies of model instances. + /// + public static class Copying + { + private static readonly ShallowCopier ShallowCopierInstance = ( + new ShallowCopier()); + + private static readonly DeepCopier DeepCopierInstance = ( + new DeepCopier()); + + /// + /// Make a shallow copy of . + /// + /// + /// All the properties are copied by reference. This includes also the lists. + /// Hence, a list property is copied by reference, and not, as sometimes might be + /// expected, as a new list of underlying references. + /// . + /// to be copied in a shallow manner + /// type to cast the result to + public static T Shallow(T that) where T : Our.IClass + { + return (T)ShallowCopierInstance.Transform(that); + } + + /// + /// Make a recursively a deep copy of . + /// + /// to be deeply copied in a recursive manner + /// type to cast the result to + public static T Deep(T that) where T : Our.IClass + { + return (T)DeepCopierInstance.Transform(that); + } + + /// + /// Make a deep copy of . + /// + private static List Deep_ListOf_IItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Deep(item)); + } + + return result; + } + + /// + /// Make a deep copy of . + /// + private static List Deep_ListOf_ISomeItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Deep(item)); + } + + return result; + } + + /// + /// Make a deep copy of . + /// + private static List Deep_ListOf_ISpecialItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Deep(item)); + } + + return result; + } + + /// Dispatch the making of shallow copies. + internal class ShallowCopier : Visitation.AbstractTransformer + { + public override Our.IClass TransformSomeItem( + Our.ISomeItem that + ) + { + return new Our.SomeItem(that.Name, that.Nickname); + } + + public override Our.IClass TransformSpecialItem( + Our.ISpecialItem that + ) + { + return new Our.SpecialItem(that.Name, that.Level, that.Nickname); + } + + public override Our.IClass TransformContainer( + Our.IContainer that + ) + { + return new Our.Container( + that.Items, + that.SomeItems, + that.SpecialItems); + } + + public override Our.IClass TransformWrapper( + Our.IWrapper that + ) + { + return new Our.Wrapper(that.Container); + } + } // internal class ShallowCopier + + /// Dispatch the making of deep copies. + internal class DeepCopier : Visitation.AbstractTransformer + { public override Our.IClass TransformSomeItem( + Our.ISomeItem that + ) + { + return new Our.SomeItem( + that.Name, + that.Nickname + ); + } + + public override Our.IClass TransformSpecialItem( + Our.ISpecialItem that + ) + { + return new Our.SpecialItem( + that.Name, + that.Level, + that.Nickname + ); + } + + public override Our.IClass TransformContainer( + Our.IContainer that + ) + { + return new Our.Container( + Deep_ListOf_IItem(that.Items), + Deep_ListOf_ISomeItem(that.SomeItems), + (that.SpecialItems != null) + ? Deep_ListOf_ISpecialItem(that.SpecialItems) + : null + ); + } + + public override Our.IClass TransformWrapper( + Our.IWrapper that + ) + { + return new Our.Wrapper( + Deep(that.Container) + ); + } + } // internal class DeepCopier + } // public static class Copying +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/enhancing.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/enhancing.cs new file mode 100644 index 000000000..1f3c3e86f --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/enhancing.cs @@ -0,0 +1,565 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Allow for enhancing of our model classes with custom wraps. + /// + public static class Enhancing + { + public abstract class Enhanced where TEnhancement : class + { + // ReSharper disable once InconsistentNaming + protected readonly TEnhancement _enhancement; + + protected Enhanced(TEnhancement enhancement) + { + _enhancement = enhancement; + } + + internal TEnhancement _getEnhancement() + { + return _enhancement; + } + } + + public class EnhancedSomeItem + : Enhanced, Our.ISomeItem + where TEnhancement : class + { + private readonly Our.ISomeItem _instance; + + public EnhancedSomeItem( + Our.ISomeItem instance, + TEnhancement enhancement + ) : base(enhancement) + { + _instance = instance; + } + + public string Name + { + get => _instance.Name; + set => _instance.Name = value; + } + + public string? Nickname + { + get => _instance.Nickname; + set => _instance.Nickname = value; + } + + public IEnumerable DescendOnce() + { + return _instance.DescendOnce(); + } + + public IEnumerable Descend() + { + return _instance.Descend(); + } + + public void Accept(Our.Visitation.IVisitor visitor) + { + visitor.VisitSomeItem(_instance); + } + + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context + ) + { + visitor.VisitSomeItem(_instance, context); + } + + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformSomeItem(_instance); + } + + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context + ) + { + return transformer.TransformSomeItem(_instance, context); + } + } + + public class EnhancedSpecialItem + : Enhanced, Our.ISpecialItem + where TEnhancement : class + { + private readonly Our.ISpecialItem _instance; + + public EnhancedSpecialItem( + Our.ISpecialItem instance, + TEnhancement enhancement + ) : base(enhancement) + { + _instance = instance; + } + + public string Name + { + get => _instance.Name; + set => _instance.Name = value; + } + + public string? Nickname + { + get => _instance.Nickname; + set => _instance.Nickname = value; + } + + public long Level + { + get => _instance.Level; + set => _instance.Level = value; + } + + public IEnumerable DescendOnce() + { + return _instance.DescendOnce(); + } + + public IEnumerable Descend() + { + return _instance.Descend(); + } + + public void Accept(Our.Visitation.IVisitor visitor) + { + visitor.VisitSpecialItem(_instance); + } + + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context + ) + { + visitor.VisitSpecialItem(_instance, context); + } + + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformSpecialItem(_instance); + } + + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context + ) + { + return transformer.TransformSpecialItem(_instance, context); + } + } + + public class EnhancedContainer + : Enhanced, Our.IContainer + where TEnhancement : class + { + private readonly Our.IContainer _instance; + + public EnhancedContainer( + Our.IContainer instance, + TEnhancement enhancement + ) : base(enhancement) + { + _instance = instance; + } + + public List Items + { + get => _instance.Items; + set => _instance.Items = value; + } + + public List SomeItems + { + get => _instance.SomeItems; + set => _instance.SomeItems = value; + } + + public List? SpecialItems + { + get => _instance.SpecialItems; + set => _instance.SpecialItems = value; + } + + public IEnumerable OverSpecialItemsOrEmpty() + { + return _instance.OverSpecialItemsOrEmpty(); + } + + public long CountItems( + List items + ) + { + return _instance.CountItems( + items + ); + } + + public IEnumerable DescendOnce() + { + return _instance.DescendOnce(); + } + + public IEnumerable Descend() + { + return _instance.Descend(); + } + + public void Accept(Our.Visitation.IVisitor visitor) + { + visitor.VisitContainer(_instance); + } + + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context + ) + { + visitor.VisitContainer(_instance, context); + } + + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformContainer(_instance); + } + + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context + ) + { + return transformer.TransformContainer(_instance, context); + } + } + + public class EnhancedWrapper + : Enhanced, Our.IWrapper + where TEnhancement : class + { + private readonly Our.IWrapper _instance; + + public EnhancedWrapper( + Our.IWrapper instance, + TEnhancement enhancement + ) : base(enhancement) + { + _instance = instance; + } + + public IContainer Container + { + get => _instance.Container; + set => _instance.Container = value; + } + + public IEnumerable DescendOnce() + { + return _instance.DescendOnce(); + } + + public IEnumerable Descend() + { + return _instance.Descend(); + } + + public void Accept(Our.Visitation.IVisitor visitor) + { + visitor.VisitWrapper(_instance); + } + + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context + ) + { + visitor.VisitWrapper(_instance, context); + } + + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformWrapper(_instance); + } + + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context + ) + { + return transformer.TransformWrapper(_instance, context); + } + } + + internal class Wrapper + : Our.Visitation.AbstractTransformer + where TEnhancement : class + { + private readonly System.Func _enhancementFactory; + + internal Wrapper( + System.Func enhancementFactory + ) + { + _enhancementFactory = enhancementFactory; + } + + public override Our.IClass TransformSomeItem( + Our.ISomeItem that + ) + { + if (that is Enhanced) + { + throw new System.ArgumentException( + $"The instance has been already enhanced: {that}" + ); + } + + var enhancement = _enhancementFactory(that); + return (enhancement == null) + ? that + : new EnhancedSomeItem( + that, + enhancement + ); + } + + public override Our.IClass TransformSpecialItem( + Our.ISpecialItem that + ) + { + if (that is Enhanced) + { + throw new System.ArgumentException( + $"The instance has been already enhanced: {that}" + ); + } + + var enhancement = _enhancementFactory(that); + return (enhancement == null) + ? that + : new EnhancedSpecialItem( + that, + enhancement + ); + } + + public override Our.IClass TransformContainer( + Our.IContainer that + ) + { + if (that is Enhanced) + { + throw new System.ArgumentException( + $"The instance has been already enhanced: {that}" + ); + } + + that.Items = Wrap_ListOf_IItem(that.Items); + + that.SomeItems = Wrap_ListOf_ISomeItem(that.SomeItems); + + if (that.SpecialItems != null) + { + that.SpecialItems = Wrap_ListOf_ISpecialItem(that.SpecialItems); + } + + var enhancement = _enhancementFactory(that); + return (enhancement == null) + ? that + : new EnhancedContainer( + that, + enhancement + ); + } + + public override Our.IClass TransformWrapper( + Our.IWrapper that + ) + { + if (that is Enhanced) + { + throw new System.ArgumentException( + $"The instance has been already enhanced: {that}" + ); + } + + that.Container = Wrap(that.Container); + + var enhancement = _enhancementFactory(that); + return (enhancement == null) + ? that + : new EnhancedWrapper( + that, + enhancement + ); + } + + /// + /// Wrap recursively and keep its static type. + /// + private T Wrap(T that) where T : Our.IClass + { + var transformed = Transform(that); + return (transformed is T casted) + ? casted + : throw new System.InvalidOperationException( + $"Expected the transformed value to be a {typeof(T).Name}, " + + $"but got: {transformed}" + ); + } + + /// + /// Wrap recursively the instances held by . + /// + private List Wrap_ListOf_IItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Wrap(item)); + } + + return result; + } + + /// + /// Wrap recursively the instances held by . + /// + private List Wrap_ListOf_ISomeItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Wrap(item)); + } + + return result; + } + + /// + /// Wrap recursively the instances held by . + /// + private List Wrap_ListOf_ISpecialItem( + List that) + { + var result = new List(that.Count); + foreach (var item in that) + { + result.Add(Wrap(item)); + } + + return result; + } + } + + /// + /// Unwrap enhancements from the wrapped instances. + /// + /// type of the enhancement + public class Unwrapper where TEnhancement : class + { + /// + /// Unwrap the given model instance. + /// + /// model instance to be unwrapped + /// + /// Enhancement, or null if + /// has not been wrapped yet. + /// + public TEnhancement? Unwrap(Our.IClass that) + { + // ReSharper disable once SuspiciousTypeConversion.Global + var enhanced = that as Enhanced; + return enhanced?._getEnhancement(); + } + + /// + /// Unwrap the given model instance. + /// + /// model instance to be unwrapped + /// + /// Enhancement wrapped around + /// + /// + /// Thrown when has not been wrapped yet + /// + public TEnhancement MustUnwrap(Our.IClass that) + { + return Unwrap(that) ?? throw new System.ArgumentException( + $"Expected the instance to have been wrapped, but it was not: {that}" + ); + } + } // public class Unwrapper + + /// + /// Wrap and unwrap the instances of model classes with enhancement. + /// + /// type of the enhancement + public class Enhancer + : Unwrapper + where TEnhancement : class + { + private readonly Wrapper _wrapper; + + /// + /// how to enhance the instances. + /// + /// If it returns null, the instance will not be wrapped. However, + /// the wrapping will continue recursively. + /// + public Enhancer( + System.Func enhancementFactory + ) + { + _wrapper = new Wrapper(enhancementFactory); + } + + /// + /// Wrap the instance with an enhancement. + /// + /// + /// Double wraps are not allowed to prevent runtime leakage. + /// + /// If you use references to the instance objects, you have to update them + /// after the wrapping, as the wrapping is recursive. + /// + /// model instance to be wrapped + /// + /// instance wrapped recursively with enhancements + /// + /// + /// Thrown when has been already wrapped + /// + public Our.IClass Wrap( + Our.IClass that + ) + { + var wrapped = _wrapper.Transform(that); + return ( + wrapped + ) ?? throw new System.InvalidOperationException( + "Expected the wrapped instance to be an instance of IClass, " + + $"but got: {wrapped}" + ); + } + } // public class Enhancer + } // public static class Enhancing +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/jsonization.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/jsonization.cs new file mode 100644 index 000000000..e7640ed64 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/jsonization.cs @@ -0,0 +1,1287 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using CodeAnalysis = System.Diagnostics.CodeAnalysis; +using Nodes = System.Text.Json.Nodes; + +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Provide de/serialization of meta-model classes to/from JSON. + /// + /// + /// We can not use one-pass deserialization for JSON since the object + /// properties do not have fixed order, and hence we can not read + /// modelType property ahead of the remaining properties. + /// + public static class Jsonization + { + /// + /// Implement the deserialization of meta-model classes from JSON nodes. + /// + /// + /// The implementation propagates an instead of relying + /// on exceptions. Under the assumption that incorrect data is much less + /// frequent than correct data, this makes the deserialization more + /// efficient. + /// + /// However, we do not want to force the client to deal with + /// the class as this is not intuitive. Therefore + /// we distinguish the implementation, realized in + /// , and the facade given in + /// class. + /// + /// Every value is de-serialized through one and the same shape, + /// Deserializer<T>, so that the de-serialization of a list or of + /// a tuple can be composed out of the de-serialization of its items. A value + /// is returned as a plain T, meaningless unless the error is + /// null, since a T? can not be written down for an unconstrained + /// T. + /// + internal static class DeserializeImplementation + { + /// + /// Describe in an error message. + /// + /// + /// A JSON null is represented as a null node, so every "expected ..., but + /// got ..." message has to account for it. Doing so here, once, is what lets + /// a de-serializer take a nullable node and reject a null itself, instead of + /// every one of its callers checking for a null before calling it. + /// + /// JSON node to be described + private static string Describe(Nodes.JsonNode? node) + { + return (node == null) + ? "a null" + : node.GetType().ToString(); + } + + /// + /// Convert to a boolean. + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static bool BoolFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonValue? value = node as Nodes.JsonValue; + if (value == null) + { + error = new Reporting.Error( + $"Expected a boolean, but got {Describe(node)}"); + return default!; + } + + bool ok = value.TryGetValue(out bool result); + if (!ok) + { + error = new Reporting.Error( + "Expected a boolean, but the conversion failed " + + $"from {value.ToJsonString()}"); + return default!; + } + return result; + } + + /// + /// Convert to a 64-bit long integer. + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static long LongFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonValue? value = node as Nodes.JsonValue; + if (value == null) + { + error = new Reporting.Error( + $"Expected a 64-bit long integer, but got {Describe(node)}"); + return default!; + } + + bool ok = value.TryGetValue(out long result); + if (!ok) + { + error = new Reporting.Error( + "Expected a 64-bit long integer, but the conversion failed " + + $"from {value.ToJsonString()}"); + return default!; + } + return result; + } + + /// + /// Convert to a 64-bit double-precision float. + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static double DoubleFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonValue? value = node as Nodes.JsonValue; + if (value == null) + { + error = new Reporting.Error( + $"Expected a 64-bit double-precision float, but got {Describe(node)}"); + return default!; + } + + bool ok = value.TryGetValue(out double result); + if (!ok) + { + error = new Reporting.Error( + "Expected a 64-bit double-precision float, but the conversion failed " + + $"from {value.ToJsonString()}"); + return default!; + } + if (!System.Double.IsFinite(result)) + { + error = new Reporting.Error( + $"Expected a finite number, but got {result}"); + return default!; + } + return result; + } + + /// + /// Convert to a string. + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static string StringFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonValue? value = node as Nodes.JsonValue; + if (value == null) + { + error = new Reporting.Error( + $"Expected a string, but got {Describe(node)}"); + return default!; + } + + bool ok = value.TryGetValue(out string? result); + if (!ok) + { + error = new Reporting.Error( + "Expected a string, but the conversion failed " + + $"from {value.ToJsonString()}"); + return default!; + } + if (result == null) + { + error = new Reporting.Error( + "Expected a string, but got a null"); + return default!; + } + return result; + } + + /// + /// Convert to Base-64 encoded bytes. + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static byte[] BytesFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonValue? value = node as Nodes.JsonValue; + if (value == null) + { + error = new Reporting.Error( + $"Expected Base-64 encoded bytes, but got {Describe(node)}"); + return default!; + } + + bool ok = value.TryGetValue(out string? text); + if (!ok) + { + error = new Reporting.Error( + "Expected Base-64 encoded bytes, but the conversion failed " + + $"from {value.ToJsonString()}"); + return default!; + } + if (text == null) + { + error = new Reporting.Error( + "Expected Base-64 encoded bytes, but got a null"); + return default!; + } + try + { + return System.Convert.FromBase64String(text); + } + catch (System.FormatException exception) + { + error = new Reporting.Error( + "Expected Base-64 encoded bytes, but the conversion failed " + + $"because: {exception}"); + return default!; + } + } + + /// + /// De-serialize a value from . + /// + /// + /// This is the one shape of every de-serialization, which is what lets + /// the de-serializations be composed: an As* combinator turns + /// the de-serializers of the items into the de-serializer of a list or + /// of a tuple of them, and the ...From function of a primitive, + /// an enumeration, a class, an interface or a named union already is one. + /// + /// Return the value; on failure it is meaningless and + /// says why. A plain T rather than + /// a T?, so that one unconstrained delegate serves both the value + /// and the reference types: for a value type an unconstrained T? + /// erases to plain T rather than to System.Nullable<T>, + /// so a T? would have to be split into a class- and + /// a struct-constrained variant, and anything ranging over both -- + /// such as the items of a tuple -- would then need an adapter between them. + /// + /// The is nullable since a JSON null is represented + /// as a null node. Each de-serializer rejects it with a message of its own, + /// so that the check is paid once per type instead of once per property. + /// + /// is covariant, so that the de-serializer of + /// a concrete class can be used as the de-serializer of an item of a list of + /// its interface. + /// + /// Type of the de-serialized value + private delegate T Deserializer( + Nodes.JsonNode? node, + out Reporting.Error? error); + + /// + /// De-serialize every item of a JSON array with + /// . + /// + /// + /// This is shared by all the list-typed constructor arguments, regardless of + /// whether their items de-serialize into a reference or into a value type. + /// The result is cached in a static readonly field per item type + /// (see Parse_ListOf_*), so that composing it costs nothing at + /// the point of use. + /// + /// Type of a single array item + private static Deserializer> AsArrayOf( + Deserializer deserializeItem) + { + return ( + Nodes.JsonNode? node, + out Reporting.Error? error) => + { + error = null; + + Nodes.JsonArray? array = node as Nodes.JsonArray; + if (array == null) + { + error = new Reporting.Error( + $"Expected a JsonArray, but got {Describe(node)}"); + return default!; + } + + List result = new List(array.Count); + + int index = 0; + foreach (Nodes.JsonNode? item in array) + { + T parsedItem = deserializeItem(item, out error); + if (error != null) + { + error.PrependSegment( + new Reporting.IndexSegment( + index)); + return default!; + } + + result.Add(parsedItem); + + index++; + } + + return result; + }; + } + + /// + /// Extract the modelType property of . + /// + /// JSON object to be inspected + /// Error, if any, during the deserialization + private static string ModelTypeFrom( + Nodes.JsonObject obj, + out Reporting.Error? error) + { + Nodes.JsonNode? modelTypeNode = obj["modelType"]; + if (modelTypeNode == null) + { + error = new Reporting.Error( + "Expected a model type, but none is present"); + return default!; + } + + string modelType = StringFrom(modelTypeNode, out error); + if (error != null) + { + // NOTE (mristin): + // A model type of a wrong type is reported at the property, as the type + // errors of all the other properties are, and as in the other SDKs. + error.PrependSegment( + new Reporting.NameSegment("modelType")); + return default!; + } + return modelType; + } + + private static readonly Deserializer> Parse_ListOf_IItem = ( + AsArrayOf( + IItemFrom)); + + private static readonly Deserializer> Parse_ListOf_ISomeItem = ( + AsArrayOf( + ISomeItemFrom)); + + private static readonly Deserializer> Parse_ListOf_ISpecialItem = ( + AsArrayOf( + SpecialItemFrom)); + + /// + /// Deserialize an instance of IItem by dispatching + /// based on modelType property of the . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + public static Our.IItem IItemFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing IItem, but got {Describe(node)}"); + return default!; + } + + string modelType = ModelTypeFrom(obj, out error); + if (error != null) + { + return default!; + } + + switch (modelType) + { + case "SomeItem": + return SomeItemFrom( + node, out error); + case "SpecialItem": + return SpecialItemFrom( + node, out error); + default: + error = new Reporting.Error( + $"Unexpected model type for IItem: {modelType}"); + return default!; + } + } // public static Our.IItem IItemFrom + + /// + /// Deserialize an instance of ISomeItem by dispatching + /// based on modelType property of the . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + public static Our.ISomeItem ISomeItemFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing ISomeItem, but got {Describe(node)}"); + return default!; + } + + string modelType = ModelTypeFrom(obj, out error); + if (error != null) + { + return default!; + } + + switch (modelType) + { + case "SpecialItem": + return SpecialItemFrom( + node, out error); + case "SomeItem": + return SomeItemFrom( + node, out error); + default: + error = new Reporting.Error( + $"Unexpected model type for ISomeItem: {modelType}"); + return default!; + } + } // public static Our.ISomeItem ISomeItemFrom + + /// + /// Deserialize an instance of SomeItem from . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static Our.SomeItem SomeItemFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing SomeItem, but got {Describe(node)}"); + return default!; + } + + string? theName = null; + string? theNickname = null; + + string? modelType = null; + + foreach (var keyValue in obj) + { + switch (keyValue.Key) + { + case "name": + theName = StringFrom( + keyValue.Value, out error); + break; + case "nickname": + theNickname = StringFrom( + keyValue.Value, out error); + break; + case "modelType": + modelType = StringFrom( + keyValue.Value, out error); + if (error == null && modelType != "SomeItem") + { + // NOTE (mristin): + // A wrong model type is reported at the instance, as an unknown one is, + // and as in the other SDKs, so we return before the property is + // prepended to the path. + error = new Reporting.Error( + "Expected the model type 'SomeItem', " + + $"but got {modelType}"); + return default!; + } + break; + default: + error = new Reporting.Error( + $"Unexpected property: {keyValue.Key}"); + return default!; + } + + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + keyValue.Key)); + return default!; + } + } + + if (theName == null) + { + error = new Reporting.Error( + "Required property \"name\" is missing"); + return default!; + } + + if (modelType == null) + { + error = new Reporting.Error( + "Required property \"modelType\" is missing"); + return default!; + } + + return new Our.SomeItem( + theName + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theNickname); + } // internal static SomeItemFrom + + /// + /// Deserialize an instance of SpecialItem from . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static Our.SpecialItem SpecialItemFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing SpecialItem, but got {Describe(node)}"); + return default!; + } + + string? theName = null; + long? theLevel = null; + string? theNickname = null; + + string? modelType = null; + + foreach (var keyValue in obj) + { + switch (keyValue.Key) + { + case "name": + theName = StringFrom( + keyValue.Value, out error); + break; + case "level": + theLevel = LongFrom( + keyValue.Value, out error); + break; + case "nickname": + theNickname = StringFrom( + keyValue.Value, out error); + break; + case "modelType": + modelType = StringFrom( + keyValue.Value, out error); + if (error == null && modelType != "SpecialItem") + { + // NOTE (mristin): + // A wrong model type is reported at the instance, as an unknown one is, + // and as in the other SDKs, so we return before the property is + // prepended to the path. + error = new Reporting.Error( + "Expected the model type 'SpecialItem', " + + $"but got {modelType}"); + return default!; + } + break; + default: + error = new Reporting.Error( + $"Unexpected property: {keyValue.Key}"); + return default!; + } + + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + keyValue.Key)); + return default!; + } + } + + if (theName == null) + { + error = new Reporting.Error( + "Required property \"name\" is missing"); + return default!; + } + + if (theLevel == null) + { + error = new Reporting.Error( + "Required property \"level\" is missing"); + return default!; + } + + if (modelType == null) + { + error = new Reporting.Error( + "Required property \"modelType\" is missing"); + return default!; + } + + return new Our.SpecialItem( + theName + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theLevel + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theNickname); + } // internal static SpecialItemFrom + + /// + /// Deserialize an instance of Container from . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static Our.Container ContainerFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing Container, but got {Describe(node)}"); + return default!; + } + + List? theItems = null; + List? theSomeItems = null; + List? theSpecialItems = null; + + foreach (var keyValue in obj) + { + switch (keyValue.Key) + { + case "items": + theItems = Parse_ListOf_IItem( + keyValue.Value, out error); + break; + case "someItems": + theSomeItems = Parse_ListOf_ISomeItem( + keyValue.Value, out error); + break; + case "specialItems": + theSpecialItems = Parse_ListOf_ISpecialItem( + keyValue.Value, out error); + break; + default: + error = new Reporting.Error( + $"Unexpected property: {keyValue.Key}"); + return default!; + } + + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + keyValue.Key)); + return default!; + } + } + + if (theItems == null) + { + error = new Reporting.Error( + "Required property \"items\" is missing"); + return default!; + } + + if (theSomeItems == null) + { + error = new Reporting.Error( + "Required property \"someItems\" is missing"); + return default!; + } + + return new Our.Container( + theItems + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theSomeItems + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theSpecialItems); + } // internal static ContainerFrom + + /// + /// Deserialize an instance of Wrapper from . + /// + /// JSON node to be parsed + /// Error, if any, during the deserialization + internal static Our.Wrapper WrapperFrom( + Nodes.JsonNode? node, + out Reporting.Error? error) + { + error = null; + + Nodes.JsonObject? obj = node as Nodes.JsonObject; + if (obj == null) + { + error = new Reporting.Error( + $"Expected a JsonObject representing Wrapper, but got {Describe(node)}"); + return default!; + } + + IContainer? theContainer = null; + + foreach (var keyValue in obj) + { + switch (keyValue.Key) + { + case "container": + theContainer = ContainerFrom( + keyValue.Value, out error); + break; + default: + error = new Reporting.Error( + $"Unexpected property: {keyValue.Key}"); + return default!; + } + + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + keyValue.Key)); + return default!; + } + } + + if (theContainer == null) + { + error = new Reporting.Error( + "Required property \"container\" is missing"); + return default!; + } + + return new Our.Wrapper( + theContainer + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before")); + } // internal static WrapperFrom + } // public static class DeserializeImplementation + + /// + /// Represent a critical error during the deserialization. + /// + public class Exception : System.Exception + { + public readonly string Path; + public readonly string Cause; + public Exception(string path, string cause) + : base($"{cause} at: {path}") + { + Path = path; + Cause = cause; + } + } + + /// + /// Represent a critical error during the serialization. + /// + public class SerializationException : System.Exception + { + public readonly string Path; + public readonly string Cause; + public SerializationException(string path, string cause) + : base($"{cause} at: {path}") + { + Path = path; + Cause = cause; + } + } + + /// + /// Signal a failure of the serialization, carrying the path to the culprit. + /// + /// + /// The path is built as the stack unwinds -- every container prepends the one + /// segment it knows, the property its name and the list the index of the item + /// -- which is why this can not be a + /// already: that one renders its message in its constructor, so its path has + /// to be complete by then. renders and + /// converts. + /// + internal class SerializationFailure : System.Exception + { + public readonly Reporting.Error Error; + public SerializationFailure(Reporting.Error error) + : base(error.Cause) + { + Error = error; + } + } + + /// + /// Deserialize instances of meta-model classes from JSON nodes. + /// + /// + /// Here is an example how to parse an instance of IItem: + /// + /// string someString = "... some JSON ..."; + /// var node = System.Text.Json.Nodes.JsonNode.Parse(someString); + /// Our.IItem anInstance = Deserialize.IItemFrom( + /// node); + /// + /// + public static class Deserialize + { + /// + /// Deserialize an instance of IItem from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of IItem. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + public static Our.IItem IItemFrom( + Nodes.JsonNode node) + { + Our.IItem result = DeserializeImplementation.IItemFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of ISomeItem from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of ISomeItem. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + public static Our.ISomeItem ISomeItemFrom( + Nodes.JsonNode node) + { + Our.ISomeItem result = DeserializeImplementation.ISomeItemFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of SomeItem from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of SomeItem. + /// + public static Our.SomeItem SomeItemFrom( + Nodes.JsonNode node) + { + Our.SomeItem result = DeserializeImplementation.SomeItemFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of SpecialItem from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of SpecialItem. + /// + public static Our.SpecialItem SpecialItemFrom( + Nodes.JsonNode node) + { + Our.SpecialItem result = DeserializeImplementation.SpecialItemFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of Container from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of Container. + /// + public static Our.Container ContainerFrom( + Nodes.JsonNode node) + { + Our.Container result = DeserializeImplementation.ContainerFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of Wrapper from . + /// + /// JSON node to be parsed + /// + /// Thrown when is not a valid JSON + /// representation of Wrapper. + /// + public static Our.Wrapper WrapperFrom( + Nodes.JsonNode node) + { + Our.Wrapper result = DeserializeImplementation.WrapperFrom( + node, + out Reporting.Error? error); + if (error != null) + { + throw new Jsonization.Exception( + Reporting.GenerateJsonPath(error.PathSegments), + error.Cause); + } + return result; + } + } // public static class Deserialize + + internal class Transformer + : Visitation.AbstractTransformer + { + /// + /// Dispatch the serialization over the run-time type of an instance. + /// + /// + /// The transformer carries no state, so a single instance serves the whole + /// program. No field initializer reads it, only + /// does, so it does not matter where among + /// the serializers it is initialized. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + private static readonly Transformer _instance = new Transformer(); + + /// + /// Serialize into a JSON object. + /// + /// + /// Which JSON object that is, is decided by the run-time type of + /// , so this one serializer serves every abstract + /// class and every concrete class with descendants, as well as the item of + /// a list or of a tuple of any of them. + /// + internal static Nodes.JsonObject TransformIClass(Our.IClass that) + { + return _instance.Transform(that); + } + + /// + /// Convert 64-bit long integer to a JSON value. + /// + /// value to be converted + /// + /// Thrown if lies outside the range where it can be + /// exactly represented as a 64-bit floating-point number, which is what + /// the JSON de-serializers of the other languages read a number into. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "UnusedMember.Local")] + private static Nodes.JsonValue ToJsonValue(long that) + { + if (that < -9007199254740991L || that > 9007199254740991L) + { + throw new SerializationFailure( + new Reporting.Error( + "The integer can not be serialized to JSON as it is outside " + + $"the range [-2^53 + 1, 2^53 - 1]: {that}")); + } + return Nodes.JsonValue.Create(that); + } + + /// + /// Convert 64-bit floating-point number to a JSON + /// value. + /// + /// value to be converted + /// + /// Thrown if is not finite. JSON knows neither + /// an infinity nor a not-a-number, so we refuse them here instead of + /// leaving it to System.Text.Json, which throws much later -- when + /// the caller writes the document out -- and says nothing about where + /// the offending value sat. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "UnusedMember.Local")] + private static Nodes.JsonValue ToJsonValue(double that) + { + if (!System.Double.IsFinite(that)) + { + throw new SerializationFailure( + new Reporting.Error( + "JSON knows neither an infinity nor a not-a-number, so " + + $"the value can not be serialized: {that}")); + } + return Nodes.JsonValue.Create(that); + } + + /// + /// Serialize into a JSON value. + /// + /// + /// This is the shape shared by every serialization step, so that the steps + /// can be composed. Unlike the XML side, no combinator is needed to frame + /// the value -- a JSON value stands on its own -- so the only composition + /// is over the items of a list or of a tuple. + /// + /// Type of the value to be serialized + private delegate Nodes.JsonNode? Serializer(T that); + + /// + /// Convert to a JSON value. + /// + private static Nodes.JsonValue ToJsonValue(string that) + { + return Nodes.JsonValue.Create(that) + ?? throw new System.InvalidOperationException( + "Unexpected null JSON value from a non-null string"); + } + + /// + /// Compose the serializer of a list whose items are serialized with + /// . + /// + /// + /// This is shared by all the list-typed properties. The composition is + /// performed once, when the field holding the result is initialized, so + /// serializing a list allocates nothing besides the JSON array itself. + /// + /// The parameter is a List rather than an IEnumerable so that + /// the iteration does not box the enumerator -- which is also the type that + /// every list-typed property actually has. + /// + /// Type of a single list item + private static Serializer> SerializeList( + Serializer serializeItem) + { + return (that) => + { + var result = new Nodes.JsonArray(); + int i = 0; + foreach (T item in that) + { + try + { + result.Add(serializeItem(item)); + } + catch (SerializationFailure failure) + { + failure.Error.PrependSegment( + new Reporting.IndexSegment(i)); + throw; + } + i++; + } + return result; + }; + } + + private static readonly Serializer> Serialize_ListOf_IItem = ( + SerializeList( + TransformIClass)); + + private static readonly Serializer> Serialize_ListOf_ISomeItem = ( + SerializeList( + TransformIClass)); + + private static readonly Serializer> Serialize_ListOf_ISpecialItem = ( + SerializeList( + TransformIClass)); + + private static readonly Serializer Serialize_string = ToJsonValue; + + private static readonly Serializer Serialize_long = ToJsonValue; + + private static readonly Serializer Serialize_IClass = TransformIClass; + + /// + /// Set the property of + /// to , serialized by + /// . + /// + /// + /// names the property on the path of + /// a failure. It is the C# property, and not the JSON one: a serialization + /// error is reported on an instance, which the caller holds, and + /// not on a document which has not been written yet. + /// + /// Type of the value to serialize + private static void SetProperty( + Nodes.JsonObject result, + string jsonName, + string propertyName, + T that, + Serializer serialize) + { + try + { + result[jsonName] = serialize(that); + } + catch (SerializationFailure failure) + { + failure.Error.PrependSegment( + new Reporting.NameSegment(propertyName)); + throw; + } + } + + public override Nodes.JsonObject TransformSomeItem( + Our.ISomeItem that + ) + { + var result = new Nodes.JsonObject(); + + SetProperty(result, "name", "Name", that.Name, Serialize_string); + + if (that.Nickname != null) + { + SetProperty(result, "nickname", "Nickname", that.Nickname, Serialize_string); + } + + result["modelType"] = "SomeItem"; + + return result; + } + + public override Nodes.JsonObject TransformSpecialItem( + Our.ISpecialItem that + ) + { + var result = new Nodes.JsonObject(); + + SetProperty(result, "name", "Name", that.Name, Serialize_string); + + if (that.Nickname != null) + { + SetProperty(result, "nickname", "Nickname", that.Nickname, Serialize_string); + } + + SetProperty(result, "level", "Level", that.Level, Serialize_long); + + result["modelType"] = "SpecialItem"; + + return result; + } + + public override Nodes.JsonObject TransformContainer( + Our.IContainer that + ) + { + var result = new Nodes.JsonObject(); + + SetProperty(result, "items", "Items", that.Items, Serialize_ListOf_IItem); + + SetProperty( + result, + "someItems", + "SomeItems", + that.SomeItems, + Serialize_ListOf_ISomeItem); + + if (that.SpecialItems != null) + { + SetProperty( + result, + "specialItems", + "SpecialItems", + that.SpecialItems, + Serialize_ListOf_ISpecialItem); + } + + return result; + } + + public override Nodes.JsonObject TransformWrapper( + Our.IWrapper that + ) + { + var result = new Nodes.JsonObject(); + + SetProperty(result, "container", "Container", that.Container, Serialize_IClass); + + return result; + } + } // internal class Transformer + + /// + /// Serialize instances of meta-model classes to JSON elements. + /// + /// + /// Here is an example how to serialize an instance of IItem: + /// + /// var anInstance = new Our.IItem( + /// // ... some constructor arguments ... + /// ); + /// System.Text.Json.Nodes.JsonObject element = ( + /// Serialize.ToJsonObject( + /// anInstance)); + /// + /// + public static class Serialize + { + /// + /// Serialize an instance of the meta-model into a JSON object. + /// + /// + /// Thrown when a value within instance can not be + /// represented in JSON + /// + public static Nodes.JsonObject ToJsonObject(Our.IClass that) + { + try + { + return Transformer.TransformIClass(that); + } + catch (SerializationFailure failure) + { + throw new SerializationException( + Reporting.GenerateCSharpPath(failure.Error.PathSegments), + failure.Error.Cause); + } + } + } // public static class Serialize + } // public static class Jsonization +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/reporting.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/reporting.cs new file mode 100644 index 000000000..4c2c4a848 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/reporting.cs @@ -0,0 +1,275 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using CodeAnalysis = System.Diagnostics.CodeAnalysis; + +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Provide reporting for de/serialization and verification. + /// + public static class Reporting + { + /// + /// Capture a path segment of a value in a model. + /// + public abstract class Segment + { + // Intentionally empty. + } + + public class NameSegment : Segment + { + public readonly string Name; + public NameSegment(string name) + { + Name = name; + } + } + + public class IndexSegment : Segment + { + public readonly int Index; + public IndexSegment(int index) + { + Index = index; + } + } + + /// + /// Capture a member of an open JSON-able object on a path to + /// the erroneous value. + /// + /// + /// Unlike a , which names a property of one of + /// our classes, a key is known only at run time, and can be any string + /// at all. + /// + public class KeySegment : Segment + { + public readonly string Key; + public KeySegment(string key) + { + Key = key; + } + } + + /// + /// Escape the characters which a JSON string may not hold as they are. + /// + private static string EscapeForJsonString( + string text) + { + var builder = new System.Text.StringBuilder(text.Length); + foreach (char character in text) + { + switch (character) + { + case '\\': + builder.Append("\\\\"); + break; + case '"': + builder.Append("\\\""); + break; + case '\b': + builder.Append("\\b"); + break; + case '\f': + builder.Append("\\f"); + break; + case '\n': + builder.Append("\\n"); + break; + case '\r': + builder.Append("\\r"); + break; + case '\t': + builder.Append("\\t"); + break; + default: + // NOTE (mristin): + // The other control characters can not stand in a JSON string as they + // are, so we write them as Unicode escapes, as JSON.stringify does. + if (character < 0x20) + { + builder.Append($"\\u{(int)character:x4}"); + } + else + { + builder.Append(character); + } + break; + } + } + return builder.ToString(); + } + + private static readonly System.Text.RegularExpressions.Regex VariableNameRe = ( + new System.Text.RegularExpressions.Regex( + @"^[a-zA-Z_][a-zA-Z_0-9]*$")); + + /// + /// Generate a JSON Path based on the path segments. + /// + /// + /// See, for example, this page for more information on JSON path: + /// https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html + /// + public static string GenerateJsonPath( + ICollection segments) + { + var parts = new List(segments.Count); + int i = 0; + foreach (var segment in segments) + { + string? part; + switch (segment) + { + case NameSegment nameSegment: + if (VariableNameRe.IsMatch(nameSegment.Name)) + { + part = (i == 0) ? nameSegment.Name : $".{nameSegment.Name}"; + } + else + { + part = ( + $"[\"{EscapeForJsonString(nameSegment.Name)}\"]"); + } + break; + case IndexSegment indexSegment: + part = $"[{indexSegment.Index}]"; + break; + case KeySegment keySegment: + // A key is no name of a property of one of our classes, so it is + // always bracketed, whatever it looks like. + part = ( + $"[\"{EscapeForJsonString(keySegment.Key)}\"]"); + break; + default: + throw new System.InvalidOperationException( + $"Unexpected segment type: {segment.GetType()}"); + } + parts.Add(part); + i++; + } + return string.Join("", parts); + } + + /// + /// Generate a C# access path based on the path segments. + /// + /// + /// The name segments are expected to denote the names of the properties in + /// C#, not the JSON property names. This is the path to report where in + /// an *instance* something went wrong -- on the serialization, say, where + /// the caller holds the instance and not a document. + /// + /// A key and an index segment need no spelling of their own: a JSON-able + /// value is a , which indexes + /// by both, so the path reads as a C# expression on the instance, *e.g.*, + /// .SomeProperty["some key"][2]. + /// + public static string GenerateCSharpPath( + ICollection segments) + { + // NOTE (mristin): + // We re-use the JSON path formatting as the implementation, but introduce + // a separate function to signal to the reader in which form the name + // segments are expected. + return GenerateJsonPath(segments); + } + + /// + /// Escape special characters for XPath. + /// + private static string EscapeForXPath( + string text) + { + // Mind the order, as we need to replace '&' first. + // + // For some benchmarks, see: + // https://stackoverflow.com/questions/1321331/replace-multiple-string-elements-in-c-sharp + return ( + text + // Even though ampersand, less-then etc. can not occur in valid element names, + // we escape them here for easier debugging and better bug reports. + .Replace("&", "&") + .Replace("/", "/") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'") + ); + } + + /// + /// Generate a relative XPath based on the path segments. + /// + /// + /// This method leaves out the leading slash ('/'). This is helpful if + /// to embed the error report in a larger document with a prefix etc. + /// + public static string GenerateRelativeXPath( + ICollection segments) + { + var parts = new List(segments.Count); + foreach (var segment in segments) + { + string? part; + switch (segment) + { + case NameSegment nameSegment: + part = EscapeForXPath(nameSegment.Name); + break; + case IndexSegment indexSegment: + // XPath counts the elements starting at one, while our indices + // start at zero. + part = $"*[{indexSegment.Index + 1}]"; + break; + case KeySegment keySegment: + // A JSON-able object is written as an XML-RPC , which + // holds the key of a member in a child element, and not in + // an attribute, so the XPath has to match on that child element. + part = ( + $"member[name=\"{EscapeForXPath(keySegment.Key)}\"]"); + break; + default: + throw new System.InvalidOperationException( + $"Unexpected segment type: {segment.GetType()}"); + } + parts.Add(part); + } + return string.Join("/", parts); + } + + /// + /// Represent an error during the deserialization or the verification. + /// + public class Error + { + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + internal readonly LinkedList _pathSegments = new LinkedList(); + public readonly string Cause; + public ICollection PathSegments => _pathSegments; + public Error(string cause) + { + Cause = cause; + } + + public void PrependSegment(Segment segment) + { + _pathSegments.AddFirst(segment); + } + } + } // public static class Reporting +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/stringification.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/stringification.cs new file mode 100644 index 000000000..df7b4ecff --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/stringification.cs @@ -0,0 +1,22 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using CodeAnalysis = System.Diagnostics.CodeAnalysis; + +using System.Collections.Generic; // can't alias + +namespace dummy +{ + public static class Stringification + { + + } // public static class Stringification +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/types.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/types.cs new file mode 100644 index 000000000..5585f7612 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/types.cs @@ -0,0 +1,578 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using EnumMemberAttribute = System.Runtime.Serialization.EnumMemberAttribute; + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias + +namespace dummy +{ + /// + /// Represent a general class of the meta-model. + /// + public interface IClass + { + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce(); + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend(); + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor); + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context); + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer); + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context); + } + + public interface IItem : IClass + { + public string Name { get; set; } + + public string? Nickname { get; set; } + } + + public interface ISomeItem : IItem + { + // Intentionally empty. + } + + public class SomeItem : ISomeItem + { + public string Name { get; set; } + + public string? Nickname { get; set; } + + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce() + { + // No descendable properties + yield break; + } + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend() + { + // No descendable properties + yield break; + } + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor) + { + visitor.VisitSomeItem(this); + } + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context) + { + visitor.VisitSomeItem(this, context); + } + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformSomeItem(this); + } + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context) + { + return transformer.TransformSomeItem(this, context); + } + + public SomeItem( + string name, + string? nickname = null) + { + Name = name; + Nickname = nickname; + } + } + + public interface ISpecialItem : ISomeItem + { + public long Level { get; set; } + } + + public class SpecialItem : ISpecialItem + { + public string Name { get; set; } + + public string? Nickname { get; set; } + + public long Level { get; set; } + + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce() + { + // No descendable properties + yield break; + } + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend() + { + // No descendable properties + yield break; + } + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor) + { + visitor.VisitSpecialItem(this); + } + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context) + { + visitor.VisitSpecialItem(this, context); + } + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformSpecialItem(this); + } + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context) + { + return transformer.TransformSpecialItem(this, context); + } + + public SpecialItem( + string name, + long level, + string? nickname = null) + { + Name = name; + Nickname = nickname; + Level = level; + } + } + + public interface IContainer : IClass + { + public List Items { get; set; } + + public List SomeItems { get; set; } + + public List? SpecialItems { get; set; } + + /// + /// Count the . + /// + /// + /// The arguments of the methods are never generic, so only the sequences of + /// can be passed to it, but not the sequences of + /// the descendants. + /// + public long CountItems(List items); + + /// + /// Iterate over SpecialItems, if set, and otherwise return an empty enumerable. + /// + public IEnumerable OverSpecialItemsOrEmpty(); + } + + public class Container : IContainer + { + public List Items { get; set; } + + public List SomeItems { get; set; } + + public List? SpecialItems { get; set; } + + /// + /// Iterate over SpecialItems, if set, and otherwise return an empty enumerable. + /// + public IEnumerable OverSpecialItemsOrEmpty() + { + return SpecialItems + ?? System.Linq.Enumerable.Empty(); + } + + /// + /// Count the . + /// + /// + /// The arguments of the methods are never generic, so only the sequences of + /// can be passed to it, but not the sequences of + /// the descendants. + /// + public long CountItems( + List items + ) + { + return items.Count; + } + + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce() + { + foreach (var anItem in Descent.DescendOnce_ListOf_IItem(Items)) + { + yield return anItem; + } + + foreach ( + var anItem in Descent.DescendOnce_ListOf_ISomeItem( + SomeItems)) + { + yield return anItem; + } + + if (SpecialItems != null) + { + foreach ( + var anItem in Descent.DescendOnce_ListOf_ISpecialItem( + SpecialItems)) + { + yield return anItem; + } + } + } + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend() + { + foreach (var anItem in Descent.Descend_ListOf_IItem(Items)) + { + yield return anItem; + } + + foreach (var anItem in Descent.Descend_ListOf_ISomeItem(SomeItems)) + { + yield return anItem; + } + + if (SpecialItems != null) + { + foreach ( + var anItem in Descent.Descend_ListOf_ISpecialItem( + SpecialItems)) + { + yield return anItem; + } + } + } + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor) + { + visitor.VisitContainer(this); + } + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context) + { + visitor.VisitContainer(this, context); + } + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformContainer(this); + } + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context) + { + return transformer.TransformContainer(this, context); + } + + public Container( + List items, + List someItems, + List? specialItems = null) + { + Items = items; + SomeItems = someItems; + SpecialItems = specialItems; + } + } + + public interface IWrapper : IClass + { + public IContainer Container { get; set; } + } + + public class Wrapper : IWrapper + { + public IContainer Container { get; set; } + + /// + /// Iterate over all the class instances referenced from this instance + /// without further recursion. + /// + public IEnumerable DescendOnce() + { + yield return Container; + } + + /// + /// Iterate recursively over all the class instances referenced from this instance. + /// + public IEnumerable Descend() + { + yield return Container; + + // Recurse + foreach (var anItem in Container.Descend()) + { + yield return anItem; + } + } + + /// + /// Accept the to visit this instance + /// for double dispatch. + /// + public void Accept(Visitation.IVisitor visitor) + { + visitor.VisitWrapper(this); + } + + /// + /// Accept the visitor to visit this instance for double dispatch + /// with the . + /// + public void Accept( + Visitation.IVisitorWithContext visitor, + TContext context) + { + visitor.VisitWrapper(this, context); + } + + /// + /// Accept the to transform this instance + /// for double dispatch. + /// + public T Transform(Visitation.ITransformer transformer) + { + return transformer.TransformWrapper(this); + } + + /// + /// Accept the to visit this instance + /// for double dispatch with the . + /// + public T Transform( + Visitation.ITransformerWithContext transformer, + TContext context) + { + return transformer.TransformWrapper(this, context); + } + + public Wrapper(IContainer container) + { + Container = container; + } + } + + /// + /// Descend into the containers which hold class instances. + /// + /// + /// Each method descends only one level, and calls the method of the items + /// by name, so that the descent is composed of plain functions to any depth. + /// + internal static class Descent + { + /// + /// Iterate recursively over all the class instances held by + /// . + /// + internal static IEnumerable Descend_ListOf_IItem( + List that) + { + foreach (var item in that) + { + yield return item; + + // Recurse + foreach (var anItem in item.Descend()) + { + yield return anItem; + } + } + } + + /// + /// Iterate over the class instances held by + /// without further recursion. + /// + internal static IEnumerable DescendOnce_ListOf_IItem( + List that) + { + foreach (var item in that) + { + yield return item; + } + } + + /// + /// Iterate recursively over all the class instances held by + /// . + /// + internal static IEnumerable Descend_ListOf_ISomeItem( + List that) + { + foreach (var item in that) + { + yield return item; + + // Recurse + foreach (var anItem in item.Descend()) + { + yield return anItem; + } + } + } + + /// + /// Iterate over the class instances held by + /// without further recursion. + /// + internal static IEnumerable DescendOnce_ListOf_ISomeItem( + List that) + { + foreach (var item in that) + { + yield return item; + } + } + + /// + /// Iterate recursively over all the class instances held by + /// . + /// + internal static IEnumerable Descend_ListOf_ISpecialItem( + List that) + { + foreach (var item in that) + { + yield return item; + + // Recurse + foreach (var anItem in item.Descend()) + { + yield return anItem; + } + } + } + + /// + /// Iterate over the class instances held by + /// without further recursion. + /// + internal static IEnumerable DescendOnce_ListOf_ISpecialItem( + List that) + { + foreach (var item in that) + { + yield return item; + } + } + } // internal static class Descent +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/verification.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/verification.cs new file mode 100644 index 000000000..ab19d73b6 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/verification.cs @@ -0,0 +1,725 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using CodeAnalysis = System.Diagnostics.CodeAnalysis; +using Regex = System.Text.RegularExpressions.Regex; + +using System.Collections.Generic; // can't alias +using System.Linq; // can't alias + +namespace dummy +{ + /// + /// Verify that the instances of the meta-model satisfy the invariants. + /// + /// + /// Here is an example how to verify an instance of IItem: + /// + /// var anInstance = new Our.IItem( + /// // ... some constructor arguments ... + /// ); + /// foreach (var error in Verification.Verify(anInstance)) + /// { + /// System.Console.Writeln( + /// $"{error.Cause} at: " + + /// Reporting.GenerateJsonPath(error.PathSegments)); + /// } + /// + /// + public static class Verification + { + /// + /// Check that the names of the are unique. + /// + /// + /// + /// The are a generic sequence, so the sequences of + /// the descendants of can be passed without a copy. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool NamesAreUnique( + IReadOnlyList items + ) + { + HashSet nameSet = new HashSet(); + foreach (var item in items) + { + if (nameSet.Contains(item.Name)) + { + return false; + } + nameSet.Add(item.Name); + } + return true; + } // public static bool NamesAreUnique + + /// + /// Iterate over a generic sequence in a generator. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + public static bool NamesAreNonEmpty( + IReadOnlyList items + ) + { + return items.All( + item => Common.StringHelpers.Len(item.Name) > 0); + } // public static bool NamesAreNonEmpty + + /// + /// Narrow the items of a generic sequence in a generator. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + public static bool SomeSpecialItemHasLevel( + IReadOnlyList items + ) + { + return items.Any( + item => item is Our.ISpecialItem + && ((Our.ISpecialItem)item).Level > 0); + } // public static bool SomeSpecialItemHasLevel + + /// + /// Index a generic sequence, and narrow the item. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + public static bool FirstIsSpecial( + IReadOnlyList items + ) + { + if (items.Count == 0) + { + return false; + } + var first = items[0]; + if (first is Our.ISpecialItem) + { + return ((Our.ISpecialItem)first).Level > 0; + } + return false; + } // public static bool FirstIsSpecial + + /// + /// Check that the nicknames of the are unique, if specified. + /// + /// + /// + /// This mirrors the body of ID_shorts_are_unique in aas-core-meta V3.2: + /// a local variable takes an optional property of an item of a generic sequence, + /// and the loop continues if it is None. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool NicknamesAreUnique( + IReadOnlyList items + ) + { + HashSet nicknameSet = new HashSet(); + foreach (var item in items) + { + var nickname = item.Nickname; + if (nickname == null) + { + continue; + } + if (nicknameSet.Contains(nickname)) + { + return false; + } + nicknameSet.Add(nickname); + } + return true; + } // public static bool NicknamesAreUnique + + /// + /// Narrow the loop variable over a generic sequence in a for-loop. + /// + /// + /// + /// The loop variable is of the type parameter in Go, so the down-cast needs to + /// go through any(...), and of the template parameter in C++. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool LevelsOfSpecialItemsArePositive( + IReadOnlyList items + ) + { + foreach (var item in items) + { + if (item is Our.ISpecialItem) + { + if (((Our.ISpecialItem)item).Level <= 0) + { + return false; + } + } + } + return true; + } // public static bool LevelsOfSpecialItemsArePositive + + /// + /// Narrow an immutable local variable which holds an item of a generic sequence. + /// + /// + /// + /// In contrast to , the local variable is annotated, so + /// it is of the parent class instead of the type parameter. Hence, Go down-casts + /// it without any(...), and C++ binds the item to a constant reference to + /// the shared pointer of the parent class. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool LastIsSpecial( + IReadOnlyList items + ) + { + if (items.Count == 0) + { + return false; + } + Our.IItem last = items[items.Count - 1]; + if (last is Our.ISpecialItem) + { + return ((Our.ISpecialItem)last).Level > 0; + } + return false; + } // public static bool LastIsSpecial + + /// + /// Mix two generic sequences with an argument which is not generic. + /// + /// + /// + /// The names must differ, except for the , which may repeat. + /// + /// + /// Go declares two type parameters, and C++ two template parameters. The nested + /// generators iterate over two different generic sequences, so their variables + /// are of two different type parameters. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + /// The argument otherItems is declared as the covariant IReadOnlyList<ISomeItem> instead of the invariant List<ISomeItem> so that the lists of the descendants of ISomeItem can be passed without a copy. + /// + /// + public static bool NamesAreDisjoint( + IReadOnlyList items, + IReadOnlyList otherItems, + string sharedName + ) + { + return items.All( + item => otherItems.All( + otherItem => item.Name != otherItem.Name + || item.Name == sharedName)); + } // public static bool NamesAreDisjoint + + /// + /// Use the immutable aliases of the generic sequences. + /// + /// + /// + /// An immutable alias of the very same type shares the type annotation of + /// the aliased generic sequence, so it is a generic sequence itself. Go declares + /// the alias with the type parameter, C++ with the template parameter, while C# + /// and Java infer its type with var. The alias_of_alias aliases an alias, + /// and the optional_alias aliases an optional generic sequence. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + /// The argument optionalItems is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool AliasedNamesAreUnique( + IReadOnlyList items, + IReadOnlyList? optionalItems + ) + { + var alias = items; + var aliasOfAlias = alias; + var optionalAlias = optionalItems; + foreach (var item in alias) + { + if (item is Our.ISpecialItem) + { + if (((Our.ISpecialItem)item).Level <= 0) + { + return false; + } + } + } + return ( + aliasOfAlias.All( + item => Common.StringHelpers.Len(item.Name) > 0) + ) + && Verification.NamesAreUnique(aliasOfAlias) + && Verification.OptionalNamesAreUnique(optionalAlias) + && ( + (optionalAlias == null) + || Verification.NamesAreUnique(optionalAlias) + ); + } // public static bool AliasedNamesAreUnique + + /// + /// Look up an instance of a descendant in a generic sequence. + /// + /// + /// + /// Go compares the instances as any's, as the items are of the type parameter, + /// and C++ converts the member to the shared pointer of so that + /// the raw pointers are comparable for any template parameter. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + public static bool ContainsSomeItem( + ISomeItem someItem, + IReadOnlyList items + ) + { + return items.Contains(someItem); + } // public static bool ContainsSomeItem + + /// + /// Look up the items of a generic sequence in another generic sequence. + /// + /// + /// + /// The member and the items are of two different type parameters. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + /// + /// The argument otherItems is declared as the covariant IReadOnlyList<ISomeItem> instead of the invariant List<ISomeItem> so that the lists of the descendants of ISomeItem can be passed without a copy. + /// + /// + public static bool ItemsOverlap( + IReadOnlyList items, + IReadOnlyList otherItems + ) + { + return otherItems.Any( + otherItem => items.Contains(otherItem)); + } // public static bool ItemsOverlap + + /// + /// Look up an instance in a sequence which is not generic. + /// + /// + /// The has no descendants, so the are not + /// a generic sequence, but Go still compares the instances as any's. + /// + public static bool ContainsSpecialItem( + ISpecialItem specialItem, + List items + ) + { + return items.Contains(specialItem); + } // public static bool ContainsSpecialItem + + /// + /// Pass the local variables on to generic sequences. + /// + /// + /// The some_items is a read-only alias without an annotation, while + /// the all_some_items is an immutable local variable declared with + /// Final[...]. Both hold the sequences of , and are passed + /// on to the generic sequences of , required as well as optional. + /// + public static bool ContainerNamesAreUnique( + IContainer container + ) + { + var someItems = container.SomeItems; + List allSomeItems = container.SomeItems; + return Verification.NamesAreUnique(someItems) + && Verification.NamesAreUnique(allSomeItems) + && Verification.OptionalNamesAreUnique(allSomeItems); + } // public static bool ContainerNamesAreUnique + + /// + /// Pass an optional generic sequence on to a generic sequence. + /// + /// + /// The argument items is declared as the covariant IReadOnlyList<IItem> instead of the invariant List<IItem> so that the lists of the descendants of IItem can be passed without a copy. + /// + public static bool OptionalNamesAreUnique( + IReadOnlyList? items + ) + { + return (items == null) + || Verification.NamesAreUnique(items); + } // public static bool OptionalNamesAreUnique + + /// + /// Pass the generic sequences of the descendants on. + /// + /// + /// + /// The are passed on as a required and as an optional + /// sequence of the parent class, while the are passed on + /// as an optional sequence and, once narrowed, as a required sequence of the parent + /// class of the parent class. + /// + /// + /// The argument someItems is declared as the covariant IReadOnlyList<ISomeItem> instead of the invariant List<ISomeItem> so that the lists of the descendants of ISomeItem can be passed without a copy. + /// + /// + public static bool SomeNamesAreUnique( + IReadOnlyList someItems, + List? specialItems + ) + { + return Verification.NamesAreUnique(someItems) + && Verification.OptionalNamesAreUnique(someItems) + && Verification.OptionalNamesAreUnique(specialItems) + && Verification.OptionalNamesAreUnique(null) + && ( + (specialItems == null) + || Verification.NamesAreUnique(specialItems) + ); + } // public static bool SomeNamesAreUnique + + /// + /// Check that the levels are positive. + /// + /// + /// The has no descendants, so the are not + /// a generic sequence. + /// + public static bool LevelsArePositive( + List items + ) + { + return items.All( + item => item.Level > 0); + } // public static bool LevelsArePositive + + /// + /// Hash allowed enum values for efficient validation of enums. + /// + internal static class EnumValueSet + { + + } // internal static class EnumValueSet + + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + private static readonly Verification.Transformer _transformer = ( + new Verification.Transformer()); + + private class Transformer + : Visitation.AbstractTransformer> + { + [CodeAnalysis.SuppressMessage("ReSharper", "NegativeEqualityExpression")] + public override IEnumerable TransformSomeItem( + Our.ISomeItem that + ) + { + // No verification has been defined for SomeItem. + yield break; + } + + [CodeAnalysis.SuppressMessage("ReSharper", "NegativeEqualityExpression")] + public override IEnumerable TransformSpecialItem( + Our.ISpecialItem that + ) + { + // No verification has been defined for SpecialItem. + yield break; + } + + [CodeAnalysis.SuppressMessage("ReSharper", "NegativeEqualityExpression")] + public override IEnumerable TransformContainer( + Our.IContainer that + ) + { + if (!( + !(that.SpecialItems != null) + || ( + that.SpecialItems.All( + specialItem => Verification.ContainsSpecialItem(specialItem, that.SpecialItems)) + ))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Each special item must be among the special items."); + } + + if (!( + that.SomeItems.Count == 0 + || Verification.ItemsOverlap(that.Items, that.SomeItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "If there are some items, they must overlap with the items."); + } + + if (!( + that.SomeItems.All( + someItem => Verification.ContainsSomeItem(someItem, that.Items)))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Some items must be among the items."); + } + + if (!( + Verification.AliasedNamesAreUnique(that.SomeItems, that.SpecialItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of some items and of special items must be unique " + + "through aliases."); + } + + if (!( + Verification.NamesAreDisjoint(that.Items, that.SomeItems, "x"))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of the items and of some items must be disjoint."); + } + + if (!( + !Verification.SomeSpecialItemHasLevel(that.Items) + || Verification.LastIsSpecial(that.Items))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "If there is a special item with level, the last item must " + + "be special."); + } + + if (!( + Verification.LevelsOfSpecialItemsArePositive(that.Items))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Levels of the special items among the items must be " + + "positive."); + } + + if (!Verification.NicknamesAreUnique(that.SomeItems)) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Nicknames of some items must be unique."); + } + + if (!(that.CountItems(that.Items) == that.Items.Count)) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Count of the items must match the number of the items."); + } + + if (!( + Verification.OptionalNamesAreUnique(that.SpecialItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of the special items must be unique."); + } + + if (!( + !(that.SpecialItems != null) + || Verification.FirstIsSpecial(that.SpecialItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "The first special item must be special."); + } + + if (!( + !(that.SpecialItems != null) + || Verification.LevelsArePositive(that.SpecialItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Levels of the special items must be positive."); + } + + if (!( + !Verification.SomeSpecialItemHasLevel(that.Items) + || Verification.FirstIsSpecial(that.Items))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "If there is a special item with level, the first item must " + + "be special."); + } + + if (!Verification.NamesAreNonEmpty(that.SomeItems)) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of some items must be non-empty."); + } + + if (!( + Verification.SomeNamesAreUnique(that.SomeItems, that.SpecialItems))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of some items and of special items must be unique."); + } + + if (!Verification.NamesAreUnique(that.Items)) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of the items must be unique."); + } + + foreach (var error in Verification.Verify_ListOf_IItem(that.Items)) + { + error.PrependSegment( + new Reporting.NameSegment("items")); + yield return error; + } + + foreach ( + var error in Verification.Verify_ListOf_ISomeItem( + that.SomeItems)) + { + error.PrependSegment( + new Reporting.NameSegment("someItems")); + yield return error; + } + + if (that.SpecialItems != null) + { + foreach ( + var error in Verification.Verify_ListOf_ISpecialItem( + that.SpecialItems)) + { + error.PrependSegment( + new Reporting.NameSegment("specialItems")); + yield return error; + } + } + } + + [CodeAnalysis.SuppressMessage("ReSharper", "NegativeEqualityExpression")] + public override IEnumerable TransformWrapper( + Our.IWrapper that + ) + { + if (!( + Verification.ContainerNamesAreUnique(that.Container))) + { + yield return new Reporting.Error( + "Invariant violated:\n" + + "Names of some items of the container must be unique."); + } + + foreach (var error in Verification.Verify(that.Container)) + { + error.PrependSegment( + new Reporting.NameSegment("container")); + yield return error; + } + } + } // private class Transformer + + /// + /// Verify the constraints of recursively. + /// + /// + /// The instance of the meta-model to be verified + /// + public static IEnumerable Verify(Our.IClass that) + { + foreach (var error in _transformer.Transform(that)) + { + yield return error; + } + } + + /// + /// Verify the items of recursively. + /// + private static IEnumerable Verify_ListOf_IItem( + List that) + { + int index = 0; + foreach (var item in that) + { + foreach (var error in Verification.Verify(item)) + { + error.PrependSegment( + new Reporting.IndexSegment(index)); + yield return error; + } + index++; + } + } + + /// + /// Verify the items of recursively. + /// + private static IEnumerable Verify_ListOf_ISomeItem( + List that) + { + int index = 0; + foreach (var item in that) + { + foreach (var error in Verification.Verify(item)) + { + error.PrependSegment( + new Reporting.IndexSegment(index)); + yield return error; + } + index++; + } + } + + /// + /// Verify the items of recursively. + /// + private static IEnumerable Verify_ListOf_ISpecialItem( + List that) + { + int index = 0; + foreach (var item in that) + { + foreach (var error in Verification.Verify(item)) + { + error.PrependSegment( + new Reporting.IndexSegment(index)); + yield return error; + } + index++; + } + } + } // public static class Verification +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/visitation.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/visitation.cs new file mode 100644 index 000000000..bd04d2a1f --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/visitation.cs @@ -0,0 +1,311 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +namespace dummy +{ + public static class Visitation + { + /// + /// Define the interface for a visitor which visits the instances of the model. + /// + /// + /// When you use the visitor, please always call the main dispatching method + /// . You should most probably never call the Visit* + /// methods directly. They are only made public so that model classes can access them. + /// + public interface IVisitor + { + public void Visit(IClass that); + public void VisitSomeItem( + ISomeItem that + ); + public void VisitSpecialItem( + ISpecialItem that + ); + public void VisitContainer( + IContainer that + ); + public void VisitWrapper( + IWrapper that + ); + } // public interface IVisitor + + /// + /// Just descend through the instances without any action. + /// + /// + /// This class is meaningless for itself. However, it is a good base if you + /// want to descend through instances and apply actions only on a subset of + /// classes. + /// + public class VisitorThrough : IVisitor + { + public virtual void Visit(IClass that) + { + that.Accept(this); + } + + public virtual void VisitSomeItem( + ISomeItem that + ) + { + // Just descend through, do nothing with that + foreach (var something in that.DescendOnce()) + { + Visit(something); + } + } + + public virtual void VisitSpecialItem( + ISpecialItem that + ) + { + // Just descend through, do nothing with that + foreach (var something in that.DescendOnce()) + { + Visit(something); + } + } + + public virtual void VisitContainer( + IContainer that + ) + { + // Just descend through, do nothing with that + foreach (var something in that.DescendOnce()) + { + Visit(something); + } + } + + public virtual void VisitWrapper( + IWrapper that + ) + { + // Just descend through, do nothing with that + foreach (var something in that.DescendOnce()) + { + Visit(something); + } + } + } // public class VisitorThrough + + /// + /// Perform double-dispatch to visit the concrete instances. + /// + public abstract class AbstractVisitor : IVisitor + { + public virtual void Visit(IClass that) + { + that.Accept(this); + } + public abstract void VisitSomeItem( + ISomeItem that + ); + public abstract void VisitSpecialItem( + ISpecialItem that + ); + public abstract void VisitContainer( + IContainer that + ); + public abstract void VisitWrapper( + IWrapper that + ); + } // public abstract class AbstractVisitor + + /// + /// Define the interface for a visitor which visits the instances of the model. + /// + /// + /// When you use the visitor, please always call the main dispatching method + /// . You should most probably never call the Visit* + /// methods directly. They are only made public so that model classes can access them. + /// + /// Context type + public interface IVisitorWithContext + { + public void Visit(IClass that, TContext context); + public void VisitSomeItem( + ISomeItem that, + TContext context + ); + public void VisitSpecialItem( + ISpecialItem that, + TContext context + ); + public void VisitContainer( + IContainer that, + TContext context + ); + public void VisitWrapper( + IWrapper that, + TContext context + ); + } // public interface IVisitorWithContext + + /// + /// Perform double-dispatch to visit the concrete instances + /// with context. + /// + /// Context type + public abstract class AbstractVisitorWithContext + : IVisitorWithContext + { + public virtual void Visit(IClass that, TContext context) + { + that.Accept(this, context); + } + public abstract void VisitSomeItem( + ISomeItem that, + TContext context + ); + public abstract void VisitSpecialItem( + ISpecialItem that, + TContext context + ); + public abstract void VisitContainer( + IContainer that, + TContext context + ); + public abstract void VisitWrapper( + IWrapper that, + TContext context + ); + } // public abstract class AbstractVisitorWithContext + + /// + /// Define the interface for a transformer which transforms recursively + /// the instances into something else. + /// + /// + /// When you use the transformer, please always call the main dispatching method + /// . You should most probably never call the Transform* + /// methods directly. They are only made public so that model classes can access them. + /// + /// The type of the transformation result + public interface ITransformer + { + public T Transform(IClass that); + public T TransformSomeItem( + ISomeItem that + ); + public T TransformSpecialItem( + ISpecialItem that + ); + public T TransformContainer( + IContainer that + ); + public T TransformWrapper( + IWrapper that + ); + } // public interface ITransformer + + /// + /// Perform double-dispatch to transform recursively + /// the instances into something else. + /// + /// The type of the transformation result + public abstract class AbstractTransformer : ITransformer + { + public virtual T Transform(IClass that) + { + return that.Transform(this); + } + + public abstract T TransformSomeItem( + ISomeItem that + ); + + public abstract T TransformSpecialItem( + ISpecialItem that + ); + + public abstract T TransformContainer( + IContainer that + ); + + public abstract T TransformWrapper( + IWrapper that + ); + } // public abstract class AbstractTransformer + + /// + /// Define the interface for a transformer which recursively transforms + /// the instances into something else while the context is passed along. + /// + /// + /// When you use the transformer, please always call the main dispatching method + /// . You should most probably never call the Transform* + /// methods directly. They are only made public so that model classes can access them. + /// + /// Type of the transformation context + /// The type of the transformation result + public interface ITransformerWithContext + { + public T Transform(IClass that, TContext context); + public T TransformSomeItem( + ISomeItem that, + TContext context + ); + public T TransformSpecialItem( + ISpecialItem that, + TContext context + ); + public T TransformContainer( + IContainer that, + TContext context + ); + public T TransformWrapper( + IWrapper that, + TContext context + ); + } // public interface ITransformerWithContext + + /// + /// Perform double-dispatch to transform recursively + /// the instances into something else. + /// + /// + /// When you use the transformer, please always call the main dispatching method + /// . You should most probably never call the Transform* + /// methods directly. They are only made public so that model classes can access them. + /// + /// The type of the transformation context + /// The type of the transformation result + public abstract class AbstractTransformerWithContext + : ITransformerWithContext + { + public virtual T Transform(IClass that, TContext context) + { + return that.Transform(this, context); + } + + public abstract T TransformSomeItem( + ISomeItem that, + TContext context + ); + + public abstract T TransformSpecialItem( + ISpecialItem that, + TContext context + ); + + public abstract T TransformContainer( + IContainer that, + TContext context + ); + + public abstract T TransformWrapper( + IWrapper that, + TContext context + ); + } // public abstract class AbstractTransformerWithContext + } // public static class Visitation +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlcommon.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlcommon.cs new file mode 100644 index 000000000..a01ce40b0 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlcommon.cs @@ -0,0 +1,319 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using Xml = System.Xml; + +namespace dummy +{ + /// + /// Provide the XML primitives shared by and + /// the de/serialization of the JSON-able values. + /// + internal static class XmlCommon + { + /// + /// XML namespace in which all the elements of a document reside + /// + /// + /// The elements of the XML-RPC subset, over which a JSON-able value is + /// de/serialized, are the one exception: they reside in no namespace at all. + /// + internal static readonly string NS = ( + "https://dummy.com"); + + /// + /// Move past the nodes which carry no + /// information -- the whitespace between the tags and the comments. + /// + internal static void SkipNoneWhitespaceAndComments( + Xml.XmlReader reader) + { + while ( + !reader.EOF + && ( + reader.NodeType == Xml.XmlNodeType.None + || reader.NodeType == Xml.XmlNodeType.Whitespace + || reader.NodeType == Xml.XmlNodeType.Comment)) + { + reader.Read(); + } + } + + /// + /// Check that the element resides in and extract + /// its name. + /// + private static string TryElementNameInNamespace( + Xml.XmlReader reader, + string ns, + out Reporting.Error? error + ) + { + // Pre-condition + if (reader.NodeType != Xml.XmlNodeType.Element + && reader.NodeType != Xml.XmlNodeType.EndElement) + { + throw new System.InvalidOperationException( + "Expected to be at a start or an end element " + + $"in {nameof(TryElementNameInNamespace)}, " + + $"but got: {reader.NodeType}"); + } + + error = null; + if (reader.NamespaceURI != ns) + { + string expected = ( + (ns.Length == 0) + ? "no namespace" + : $"the namespace {ns}"); + string got = ( + (reader.NamespaceURI.Length == 0) + ? "no namespace" + : $"the namespace {reader.NamespaceURI}"); + + error = new Reporting.Error( + $"Expected an element within {expected}, " + + $"but got an element within {got}"); + return ""; + } + + return reader.LocalName; + } + + /// + /// Consume the start tag of an element named + /// and tell whether that element was + /// self-closing and hence has no content and no end tag. + /// + /// + /// On failure the returned value is meaningless; the caller checks + /// first. + /// + private static bool ReadStartElementInNamespace( + Xml.XmlReader reader, + string ns, + string expectedName, + out Reporting.Error? error + ) + { + error = null; + + SkipNoneWhitespaceAndComments(reader); + + if (reader.EOF) + { + error = new Reporting.Error( + $"Expected a <{expectedName}> element, " + + "but reached the end-of-file"); + return false; + } + + if (reader.NodeType != Xml.XmlNodeType.Element) + { + error = new Reporting.Error( + $"Expected a <{expectedName}> element, " + + $"but got a node of type {reader.NodeType} " + + $"with value {reader.Value}"); + return false; + } + + string observedName = TryElementNameInNamespace( + reader, ns, out error); + if (error != null) + { + return false; + } + + if (observedName != expectedName) + { + error = new Reporting.Error( + $"Expected a <{expectedName}> element, " + + $"but got a <{observedName}> element"); + return false; + } + + bool isEmptyElement = reader.IsEmptyElement; + + // Consume the start tag and go to the content. + reader.Read(); + + return isEmptyElement; + } + + /// + /// Consume the end tag matching , unless + /// tells that the element was + /// self-closing and thus has no end tag at all. + /// + private static void ConsumeEndElementInNamespace( + Xml.XmlReader reader, + string ns, + string elementName, + bool isEmptyElement, + out Reporting.Error? error + ) + { + error = null; + + if (isEmptyElement) + { + return; + } + + SkipNoneWhitespaceAndComments(reader); + + if (reader.EOF) + { + error = new Reporting.Error( + $"Expected a closing element , " + + "but reached the end-of-file"); + return; + } + + if (reader.NodeType != Xml.XmlNodeType.EndElement) + { + error = new Reporting.Error( + $"Expected a closing element , " + + $"but got a node of type {reader.NodeType} " + + $"with value {reader.Value}"); + return; + } + + string endElementName = TryElementNameInNamespace( + reader, ns, out error); + if (error != null) + { + return; + } + + if (endElementName != elementName) + { + error = new Reporting.Error( + $"Expected a closing element , " + + $"but got a closing element "); + return; + } + + // Consume the end tag. + reader.Read(); + } + + /// + /// Check that the element resides in and extract its name. + /// + internal static string TryElementName( + Xml.XmlReader reader, + out Reporting.Error? error + ) + { + return TryElementNameInNamespace(reader, NS, out error); + } + + /// + /// Consume the start tag of an element named + /// in , and tell whether + /// that element was self-closing. + /// + internal static bool ReadStartElement( + Xml.XmlReader reader, + string expectedName, + out Reporting.Error? error + ) + { + return ReadStartElementInNamespace( + reader, NS, expectedName, out error); + } + + /// + /// Consume the end tag matching in + /// , unless the element was self-closing. + /// + internal static void ConsumeEndElement( + Xml.XmlReader reader, + string elementName, + bool isEmptyElement, + out Reporting.Error? error + ) + { + ConsumeEndElementInNamespace( + reader, NS, elementName, isEmptyElement, out error); + } + + /// + /// Look ahead the name of the element at the current position of + /// , without consuming anything. + /// + internal static string PeekElementName( + Xml.XmlReader reader, + out Reporting.Error? error + ) + { + error = null; + + SkipNoneWhitespaceAndComments(reader); + + if (reader.EOF) + { + error = new Reporting.Error( + "Expected an XML element, but reached the end-of-file"); + return ""; + } + + if (reader.NodeType != Xml.XmlNodeType.Element) + { + error = new Reporting.Error( + "Expected an XML element, " + + $"but got a node of type {reader.NodeType} " + + $"with value {reader.Value}"); + return ""; + } + + return TryElementNameInNamespace( + reader, NS, out error); + } + } // internal static class XmlCommon + + /// + /// Signal a failure of the serialization, carrying the path to the culprit. + /// + /// + /// The path is built as the stack unwinds -- every container prepends the one + /// segment it knows, the property its name and the list the index of the item + /// -- which is why this can not be a + /// already: that one renders its message in its constructor, so its path has + /// to be complete by then. renders and + /// converts. + /// + internal class SerializationFailure : System.Exception + { + public readonly Reporting.Error Error; + public SerializationFailure(Reporting.Error error) + : base(error.Cause) + { + Error = error; + } + } + + /// + /// Represent a critical error during the serialization to XML. + /// + public class SerializationException : System.Exception + { + public readonly string Path; + public readonly string Cause; + public SerializationException(string path, string cause) + : base($"{cause} at: {(path == "" ? "the instance itself" : path)}") + { + Path = path; + Cause = cause; + } + } +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlization.cs b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlization.cs new file mode 100644 index 000000000..aad2d8e53 --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/dummy/xmlization.cs @@ -0,0 +1,1599 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +using Our = dummy; // renamed +using CodeAnalysis = System.Diagnostics.CodeAnalysis; +using Xml = System.Xml; + +using System.Collections.Generic; // can't alias + +namespace dummy +{ + /// + /// Provide de/serialization of meta-model classes to/from XML. + /// + public static class Xmlization + { + /// The XML namespace of the meta-model + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + public static readonly string NS = XmlCommon.NS; + + /// + /// Implement the deserialization of meta-model classes from XML. + /// + /// + /// The implementation propagates an instead of + /// relying on exceptions. Under the assumption that incorrect data is much less + /// frequent than correct data, this makes the deserialization more + /// efficient. + /// + /// However, we do not want to force the client to deal with + /// the class as this is not intuitive. + /// Therefore we distinguish the implementation, realized in + /// , and the facade given in + /// class. + /// + internal static class DeserializeImplementation + { + /// + /// Read a single element, tags included, positioned at its start tag. + /// + /// + /// Return the value; on failure it is meaningless and + /// says why. A plain T rather than + /// a T?, so that one unconstrained delegate serves both the value + /// and the reference types. + /// + /// is covariant, so that the reader of + /// a concrete class can be used as the reader of an item of a list of + /// its interface. It is internal only because the readers of + /// the classes are, and a field may not be more accessible than its type. + /// + /// Type of the parsed value + internal delegate T ElementReader( + Xml.XmlReader reader, + out Reporting.Error? error); + + /// + /// Read the content of an element, positioned after its start tag. + /// + /// + /// Every value is read through this one shape, so that the reading can be + /// composed: an As* combinator turns a conversion, a literal parser, + /// an element reader or a list of them into one of these, and a class's own + /// ...FromSequence already is one. + /// + /// Type of the value + private delegate T ContentReader( + Xml.XmlReader reader, + bool isEmpty, + out Reporting.Error? error); + + /// + /// Convert the content at the current position of . + /// + /// Type to convert the content to + private delegate T ContentConverter(Xml.XmlReader reader); + + /// + /// Read the content between a start and an end tag and convert it + /// with . + /// + /// + /// This is the one skeleton for reading any content whatsoever -- of + /// a property, or of a <v> element of a list or a tuple item + /// (see ). Only the conversion differs, so + /// only the conversion is passed in. + /// + /// On failure the returned value is meaningless; the caller checks + /// and bails out before ever reading it. That is + /// what lets this return a plain T -- a T? would have to be + /// split into a variant for the value and one for the reference types. + /// + /// Type of the value + private static ContentReader AsText( + ContentConverter readContent + ) + { + return ( + Xml.XmlReader reader, + bool isEmpty, + out Reporting.Error? error + ) => + { + error = null; + + if (isEmpty) + { + error = new Reporting.Error( + $"Expected an XML content representing {typeof(T).Name}, " + + "but the element was self-closing"); + return default!; + } + + if (reader.EOF) + { + error = new Reporting.Error( + $"Expected an XML content representing {typeof(T).Name}, " + + "but reached the end-of-file"); + return default!; + } + + // NOTE (mristin): + // XmlReader.ReadContentAs* throws an InvalidOperationException if it + // starts at an element, so we have to report a nested element before + // the conversion. A nested element after a text is not our concern + // here, as the caller expects the end element afterwards. + if (reader.NodeType == Xml.XmlNodeType.Element) + { + error = new Reporting.Error( + $"Expected an XML content representing {typeof(T).Name}, " + + $"but got a nested element {reader.Name}"); + return default!; + } + + try + { + return readContent(reader); + } + catch (System.Exception exception) + when (exception is System.FormatException + || exception is System.Xml.XmlException + // NOTE (mristin): + // An integer beyond the range of a long leaves + // ReadContentAsLong as an OverflowException, which is neither + // of the two above, so it used to escape this filter and leave + // the de-serialization through an exception we never declared. + || exception is System.OverflowException) + { + error = new Reporting.Error( + $"The content could not be de-serialized as {typeof(T).Name}: " + + exception.Message); + return default!; + } + }; + } + + /// + /// Read the content between a start and an end tag, or return + /// if the element was self-closing. + /// + /// Type of the value + [CodeAnalysis.SuppressMessage("ReSharper", "UnusedMember.Local")] + private static ContentReader AsText( + ContentConverter readContent, + T whenEmpty + ) + { + ContentReader readText = AsText(readContent); + + return ( + Xml.XmlReader reader, + bool isEmpty, + out Reporting.Error? error + ) => + { + if (isEmpty) + { + error = null; + return whenEmpty; + } + + return readText(reader, false, out error); + }; + } + + /// + /// Convert the content at the current position of + /// to long. + /// + private static long ReadContentAsLong(Xml.XmlReader reader) + { + return reader.ReadContentAsLong(); + } + + /// + /// Convert the content at the current position of + /// to string. + /// + private static string ReadContentAsString(Xml.XmlReader reader) + { + return reader.ReadContentAsString(); + } + + /// + /// Bind to , + /// so that the result reads the whole element, tags included. + /// + /// Type of the parsed value + private static ElementReader AtElement( + ContentReader readContent, + string elementName + ) + { + return ( + Xml.XmlReader reader, + out Reporting.Error? error + ) => + { + bool isEmptyElement = XmlCommon.ReadStartElement( + reader, elementName, out error); + if (error != null) + { + return default!; + } + + T value = readContent(reader, isEmptyElement, out error); + if (error != null) + { + return default!; + } + + XmlCommon.ConsumeEndElement( + reader, elementName, isEmptyElement, out error); + if (error != null) + { + return default!; + } + + return value; + }; + } + + /// + /// Read the start tag of the next property of a sequence and return whether + /// there was one. + /// + /// + /// A sequence ends at the end tag of the enclosing element or at the end of + /// the file, which is not a failure -- when this returns false, + /// tells the two apart. + /// + /// The start tag is consumed, so the reader is left at the content of + /// the property. + /// + private static bool TryNextProperty( + Xml.XmlReader reader, + out string elementName, + out bool isEmptyProperty, + out Reporting.Error? error + ) + { + error = null; + elementName = ""; + isEmptyProperty = false; + + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (reader.NodeType == Xml.XmlNodeType.EndElement || reader.EOF) + { + return false; + } + + if (reader.NodeType != Xml.XmlNodeType.Element) + { + error = new Reporting.Error( + "Expected an XML start element representing a property, " + + $"but got the node of type {reader.NodeType} " + + $"with the value {reader.Value}"); + return false; + } + + elementName = XmlCommon.TryElementName( + reader, out error); + if (error != null) + { + return false; + } + + isEmptyProperty = reader.IsEmptyElement; + + // Consume the start tag and go to the content. + reader.Read(); + + return true; + } + + /// + /// Report a property which the sequence of the properties gave more than once. + /// + private static Reporting.Error DuplicatePropertyError(string elementName) + { + return new Reporting.Error( + $"Property {elementName} occurred more than once"); + } + + /// + /// Read a sequence of list items with , + /// stopping (without consuming) at the first non-element node. + /// + /// + /// This is shared by everything of a list type, whatever its items are and + /// however deeply it is nested, since the items are read through + /// an like any other element. + /// + /// Type of a single list item + private static List ReadList( + Xml.XmlReader reader, + ElementReader readItem, + out Reporting.Error? error + ) + { + error = null; + var result = new List(); + + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + int index = 0; + while (reader.NodeType == Xml.XmlNodeType.Element) + { + T item = readItem(reader, out error); + if (error != null) + { + error.PrependSegment( + new Reporting.IndexSegment( + index)); + return result; + } + + result.Add(item); + + index++; + XmlCommon.SkipNoneWhitespaceAndComments(reader); + } + + return result; + } + + /// + /// Read a content as a list of items, each read with + /// . + /// + /// + /// A self-closing element represents an empty list. + /// + /// Type of a single list item + private static ContentReader> AsList( + ElementReader readItem + ) + { + return ( + Xml.XmlReader reader, + bool isEmpty, + out Reporting.Error? error + ) => + { + error = null; + + if (isEmpty) + { + return new List(); + } + + return ReadList( + reader, readItem, out error); + }; + } + + /// + /// Read an instance of class SomeItem from its XML element. + /// + internal static readonly ElementReader SomeItemFromElement = ( + AtElement( + SomeItemFromSequence, "someItem")); + + /// + /// Read an instance of class SpecialItem from its XML element. + /// + internal static readonly ElementReader SpecialItemFromElement = ( + AtElement( + SpecialItemFromSequence, "specialItem")); + + /// + /// Read an instance of class Container from its XML element. + /// + internal static readonly ElementReader ContainerFromElement = ( + AtElement( + ContainerFromSequence, "container")); + + /// + /// Read an instance of class Wrapper from its XML element. + /// + internal static readonly ElementReader WrapperFromElement = ( + AtElement( + WrapperFromSequence, "wrapper")); + + private static readonly ContentReader Read_string = ( + AsText(ReadContentAsString, "")); + + private static readonly ContentReader Read_long = ( + AsText(ReadContentAsLong)); + + private static readonly ContentReader> Read_ListOf_IItem = ( + AsList( + IItemFromElement)); + + private static readonly ContentReader> Read_ListOf_ISomeItem = ( + AsList( + ISomeItemFromElement)); + + private static readonly ContentReader> Read_ListOf_ISpecialItem = ( + AsList( + SpecialItemFromElement)); + + private static readonly ContentReader Read_IContainer = ( + ContainerFromSequence); + + /// + /// Deserialize an instance of IItem from an XML element. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + internal static Our.IItem IItemFromElement( + Xml.XmlReader reader, + out Reporting.Error? error) + { + string elementName = XmlCommon.PeekElementName( + reader, out error); + if (error != null) + { + return default!; + } + + switch (elementName) + { + case "someItem": + return SomeItemFromElement( + reader, out error); + case "specialItem": + return SpecialItemFromElement( + reader, out error); + default: + error = new Reporting.Error( + $"Unexpected element with the name {elementName}"); + return default!; + } + } // internal static Our.IItem? IItemFromElement + + /// + /// Deserialize an instance of class SomeItem from a sequence of XML elements. + /// + /// + /// If is set, we should try to deserialize + /// the instance from an empty sequence. That is, the parent element + /// was a self-closing element. + /// + internal static Our.SomeItem SomeItemFromSequence( + Xml.XmlReader reader, + bool isEmptySequence, + out Reporting.Error? error) + { + error = null; + + string? theName = null; + string? theNickname = null; + + if (!isEmptySequence) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + if (reader.EOF) + { + error = new Reporting.Error( + "Expected an XML element representing " + + "a property of an instance of class SomeItem, " + + "but reached the end-of-file"); + return default!; + } + while (TryNextProperty( + reader, + out string elementName, + out bool isEmptyProperty, + out error)) + { + switch (elementName) + { + case "name": + if (theName != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theName = Read_string( + reader, isEmptyProperty, out error); + break; + case "nickname": + if (theNickname != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theNickname = Read_string( + reader, isEmptyProperty, out error); + break; + default: + error = new Reporting.Error( + "We expected properties of the class SomeItem, " + + "but got an unexpected element " + + $"with the name {elementName}"); + return default!; + } + + if (error == null) + { + XmlCommon.ConsumeEndElement( + reader, elementName, isEmptyProperty, out error); + } + + // NOTE (mristin): + // Every property is read in this very loop, so we mark the error with + // the property's own element name here, once, instead of at every + // single case above. For a matched case, elementName *is* that name. + // This covers the end element of the property as well, since whatever + // unexpected node precedes it still lies within the property. + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + elementName)); + return default!; + } + } + + // NOTE (mristin): + // The loop also ends when the next property could not be read at all, + // which is the only way out of it that is a failure. + if (error != null) + { + return default!; + } + } + + if (theName == null) + { + error = new Reporting.Error( + "The required property Name has not been given " + + "in the XML representation of an instance of class SomeItem"); + return default!; + } + + return new Our.SomeItem( + theName + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theNickname); + } // internal static Our.SomeItem? SomeItemFromSequence + + /// + /// Deserialize an instance of ISomeItem from an XML element. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + internal static Our.ISomeItem ISomeItemFromElement( + Xml.XmlReader reader, + out Reporting.Error? error) + { + string elementName = XmlCommon.PeekElementName( + reader, out error); + if (error != null) + { + return default!; + } + + switch (elementName) + { + case "specialItem": + return SpecialItemFromElement( + reader, out error); + case "someItem": + return SomeItemFromElement( + reader, out error); + default: + error = new Reporting.Error( + $"Unexpected element with the name {elementName}"); + return default!; + } + } // internal static Our.ISomeItem? ISomeItemFromElement + + /// + /// Deserialize an instance of class SpecialItem from a sequence of XML elements. + /// + /// + /// If is set, we should try to deserialize + /// the instance from an empty sequence. That is, the parent element + /// was a self-closing element. + /// + internal static Our.SpecialItem SpecialItemFromSequence( + Xml.XmlReader reader, + bool isEmptySequence, + out Reporting.Error? error) + { + error = null; + + string? theName = null; + string? theNickname = null; + long? theLevel = null; + + if (!isEmptySequence) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + if (reader.EOF) + { + error = new Reporting.Error( + "Expected an XML element representing " + + "a property of an instance of class SpecialItem, " + + "but reached the end-of-file"); + return default!; + } + while (TryNextProperty( + reader, + out string elementName, + out bool isEmptyProperty, + out error)) + { + switch (elementName) + { + case "name": + if (theName != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theName = Read_string( + reader, isEmptyProperty, out error); + break; + case "nickname": + if (theNickname != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theNickname = Read_string( + reader, isEmptyProperty, out error); + break; + case "level": + if (theLevel != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theLevel = Read_long( + reader, isEmptyProperty, out error); + break; + default: + error = new Reporting.Error( + "We expected properties of the class SpecialItem, " + + "but got an unexpected element " + + $"with the name {elementName}"); + return default!; + } + + if (error == null) + { + XmlCommon.ConsumeEndElement( + reader, elementName, isEmptyProperty, out error); + } + + // NOTE (mristin): + // Every property is read in this very loop, so we mark the error with + // the property's own element name here, once, instead of at every + // single case above. For a matched case, elementName *is* that name. + // This covers the end element of the property as well, since whatever + // unexpected node precedes it still lies within the property. + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + elementName)); + return default!; + } + } + + // NOTE (mristin): + // The loop also ends when the next property could not be read at all, + // which is the only way out of it that is a failure. + if (error != null) + { + return default!; + } + } + + if (theName == null) + { + error = new Reporting.Error( + "The required property Name has not been given " + + "in the XML representation of an instance of class SpecialItem"); + return default!; + } + + if (theLevel == null) + { + error = new Reporting.Error( + "The required property Level has not been given " + + "in the XML representation of an instance of class SpecialItem"); + return default!; + } + + return new Our.SpecialItem( + theName + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theLevel + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theNickname); + } // internal static Our.SpecialItem? SpecialItemFromSequence + + /// + /// Deserialize an instance of class Container from a sequence of XML elements. + /// + /// + /// If is set, we should try to deserialize + /// the instance from an empty sequence. That is, the parent element + /// was a self-closing element. + /// + internal static Our.Container ContainerFromSequence( + Xml.XmlReader reader, + bool isEmptySequence, + out Reporting.Error? error) + { + error = null; + + List? theItems = null; + List? theSomeItems = null; + List? theSpecialItems = null; + + if (!isEmptySequence) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + if (reader.EOF) + { + error = new Reporting.Error( + "Expected an XML element representing " + + "a property of an instance of class Container, " + + "but reached the end-of-file"); + return default!; + } + while (TryNextProperty( + reader, + out string elementName, + out bool isEmptyProperty, + out error)) + { + switch (elementName) + { + case "items": + if (theItems != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theItems = Read_ListOf_IItem( + reader, isEmptyProperty, out error); + break; + case "someItems": + if (theSomeItems != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theSomeItems = Read_ListOf_ISomeItem( + reader, isEmptyProperty, out error); + break; + case "specialItems": + if (theSpecialItems != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theSpecialItems = Read_ListOf_ISpecialItem( + reader, isEmptyProperty, out error); + break; + default: + error = new Reporting.Error( + "We expected properties of the class Container, " + + "but got an unexpected element " + + $"with the name {elementName}"); + return default!; + } + + if (error == null) + { + XmlCommon.ConsumeEndElement( + reader, elementName, isEmptyProperty, out error); + } + + // NOTE (mristin): + // Every property is read in this very loop, so we mark the error with + // the property's own element name here, once, instead of at every + // single case above. For a matched case, elementName *is* that name. + // This covers the end element of the property as well, since whatever + // unexpected node precedes it still lies within the property. + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + elementName)); + return default!; + } + } + + // NOTE (mristin): + // The loop also ends when the next property could not be read at all, + // which is the only way out of it that is a failure. + if (error != null) + { + return default!; + } + } + + if (theItems == null) + { + error = new Reporting.Error( + "The required property Items has not been given " + + "in the XML representation of an instance of class Container"); + return default!; + } + + if (theSomeItems == null) + { + error = new Reporting.Error( + "The required property SomeItems has not been given " + + "in the XML representation of an instance of class Container"); + return default!; + } + + return new Our.Container( + theItems + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theSomeItems + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before"), + theSpecialItems); + } // internal static Our.Container? ContainerFromSequence + + /// + /// Deserialize an instance of class Wrapper from a sequence of XML elements. + /// + /// + /// If is set, we should try to deserialize + /// the instance from an empty sequence. That is, the parent element + /// was a self-closing element. + /// + internal static Our.Wrapper WrapperFromSequence( + Xml.XmlReader reader, + bool isEmptySequence, + out Reporting.Error? error) + { + error = null; + + IContainer? theContainer = null; + + if (!isEmptySequence) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + if (reader.EOF) + { + error = new Reporting.Error( + "Expected an XML element representing " + + "a property of an instance of class Wrapper, " + + "but reached the end-of-file"); + return default!; + } + while (TryNextProperty( + reader, + out string elementName, + out bool isEmptyProperty, + out error)) + { + switch (elementName) + { + case "container": + if (theContainer != null) + { + error = DuplicatePropertyError(elementName); + break; + } + theContainer = Read_IContainer( + reader, isEmptyProperty, out error); + break; + default: + error = new Reporting.Error( + "We expected properties of the class Wrapper, " + + "but got an unexpected element " + + $"with the name {elementName}"); + return default!; + } + + if (error == null) + { + XmlCommon.ConsumeEndElement( + reader, elementName, isEmptyProperty, out error); + } + + // NOTE (mristin): + // Every property is read in this very loop, so we mark the error with + // the property's own element name here, once, instead of at every + // single case above. For a matched case, elementName *is* that name. + // This covers the end element of the property as well, since whatever + // unexpected node precedes it still lies within the property. + if (error != null) + { + error.PrependSegment( + new Reporting.NameSegment( + elementName)); + return default!; + } + } + + // NOTE (mristin): + // The loop also ends when the next property could not be read at all, + // which is the only way out of it that is a failure. + if (error != null) + { + return default!; + } + } + + if (theContainer == null) + { + error = new Reporting.Error( + "The required property Container has not been given " + + "in the XML representation of an instance of class Wrapper"); + return default!; + } + + return new Our.Wrapper( + theContainer + ?? throw new System.InvalidOperationException( + "Unexpected null, had to be handled before")); + } // internal static Our.Wrapper? WrapperFromSequence + } // internal static class DeserializeImplementation + + /// + /// Represent a critical error during the deserialization. + /// + public class Exception : System.Exception + { + public readonly string Path; + public readonly string Cause; + public Exception(string path, string cause) + : base($"{cause} at: {(path == "" ? "the beginning" : path)}") + { + Path = path; + Cause = cause; + } + } + + /// + /// Deserialize instances of meta-model classes from XML. + /// + /// + /// Here is an example how to parse an instance of class IItem: + /// + /// var reader = new System.Xml.XmlReader(/* some arguments */); + /// Our.IItem anInstance = Deserialize.IItemFrom( + /// reader); + /// + /// + /// + /// + /// The elements are expected to live in , the one XML + /// namespace of the meta-model, so there is nothing to supply. + /// + public static class Deserialize + { + /// + /// Deserialize an instance of IItem from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of IItem. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]public static Our.IItem IItemFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class IItem, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.IItem result = DeserializeImplementation.IItemFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of ISomeItem from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of ISomeItem. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")]public static Our.ISomeItem ISomeItemFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class ISomeItem, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.ISomeItem result = DeserializeImplementation.ISomeItemFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of SomeItem from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of SomeItem. + /// + public static Our.SomeItem SomeItemFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class SomeItem, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.SomeItem result = DeserializeImplementation.SomeItemFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of SpecialItem from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of SpecialItem. + /// + public static Our.SpecialItem SpecialItemFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class SpecialItem, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.SpecialItem result = DeserializeImplementation.SpecialItemFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of Container from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of Container. + /// + public static Our.Container ContainerFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class Container, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.Container result = DeserializeImplementation.ContainerFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + + /// + /// Deserialize an instance of Wrapper from . + /// + /// Initialized XML reader with cursor set to the element + /// + /// Thrown when the element is not a valid XML + /// representation of Wrapper. + /// + public static Our.Wrapper WrapperFrom( + Xml.XmlReader reader) + { + XmlCommon.SkipNoneWhitespaceAndComments(reader); + + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.XmlDeclaration) + { + throw new Xmlization.Exception( + "", + "Unexpected XML declaration when reading an instance " + + "of class Wrapper, as we expect the reader " + + "to be set at content with MoveToContent"); + } + + // NOTE (mristin): + // The path starts at the root element. The readers do not prepend their own + // names, so we prepend the name of the root element here, if there is any. + string? rootElementName = null; + if (!reader.EOF && reader.NodeType == Xml.XmlNodeType.Element) + { + rootElementName = reader.LocalName; + } + + Our.Wrapper result = DeserializeImplementation.WrapperFromElement( + reader, + out Reporting.Error? error); + if (error != null) + { + if (rootElementName != null) + { + error.PrependSegment( + new Reporting.NameSegment(rootElementName)); + } + throw new Xmlization.Exception( + Reporting.GenerateRelativeXPath(error.PathSegments), + error.Cause); + } + return result; + } + } // public static class Deserialize + + /// + /// Serialize recursively the instances as XML elements. + /// + internal class VisitorWithWriter + : Visitation.AbstractVisitorWithContext + { + /// + /// Write where already + /// is. + /// + /// + /// Every value is written through this one shape, so that the writing can + /// be composed: a Write* combinator turns a stringification, a list + /// or a tuple of them into one of these, and a class's own + /// ...ToSequence already is one. + /// + /// There is deliberately no second delegate for a whole element: an element + /// differs from a content only in what it writes, never in its shape, and + /// WrapInElement converts between the two. + /// + /// is contravariant, so that + /// can be used wherever the writer of a more + /// specific interface is expected. + /// + /// Type of the value to write + private delegate void ContentWriter( + T that, + Xml.XmlWriter writer); + + /// + /// Write as an XML element named + /// , its content written by + /// . + /// + /// + /// An element is nothing but a start and an end tag around a content, so + /// there is no writer per property kind -- only the content differs, and it + /// has been composed once into a field. + /// + /// Type of the value to write + private static void WriteElement( + string elementName, + T that, + Xml.XmlWriter writer, + ContentWriter writeContent) + { + writer.WriteStartElement(elementName, NS); + writeContent(that, writer); + writer.WriteEndElement(); + } + + /// + /// Write the property of the instance being + /// serialized as an XML element named . + /// + /// + /// This is plus the one segment of the path + /// which only the property knows. The path names the C# property, and not + /// the XML element: a serialization error is reported on an instance, + /// which the caller holds, and not on a document which has not been written + /// yet. + /// + /// Type of the value to write + private static void WriteProperty( + string elementName, + string propertyName, + T that, + Xml.XmlWriter writer, + ContentWriter writeContent) + { + try + { + WriteElement(elementName, that, writer, writeContent); + } + catch (SerializationFailure failure) + { + failure.Error.PrependSegment( + new Reporting.NameSegment(propertyName)); + throw; + } + } + + /// + /// Write the items of a list, each with . + /// + /// + /// An empty list writes no items at all, which the reading sees as + /// a self-closing element. + /// + /// Type of a single list item + private static ContentWriter> WriteList( + ContentWriter writeItem + ) + { + return (that, writer) => + { + int index = 0; + foreach (var item in that) + { + try + { + writeItem(item, writer); + } + catch (SerializationFailure failure) + { + failure.Error.PrependSegment( + new Reporting.IndexSegment(index)); + throw; + } + index++; + } + }; + } + + /// + /// The one instance through which the writing is dispatched. + /// + /// + /// The visitor carries no state -- the writer is passed in as the context -- + /// so a single instance serves the whole program. No field initializer reads + /// it, only does, so it does not matter where + /// among the writers it is initialized. + /// + [CodeAnalysis.SuppressMessage("ReSharper", "InconsistentNaming")] + private static readonly VisitorWithWriter _instance = ( + new VisitorWithWriter()); + + /// + /// Write as its own XML element. + /// + /// + /// Which element that is, is decided by the run-time type of + /// , so this one writer serves every abstract class + /// and every concrete class with descendants, as well as the item of a list + /// or of a tuple of any of them. + /// + internal static void WriteIClass( + Our.IClass that, + Xml.XmlWriter writer) + { + that.Accept(_instance, writer); + } + + private static readonly ContentWriter Write_string = ( + (that, writer) => writer.WriteValue(that)); + + private static readonly ContentWriter Write_long = ( + (that, writer) => writer.WriteValue(that)); + + private static readonly ContentWriter> Write_ListOf_IItem = ( + WriteList( + WriteIClass)); + + private static readonly ContentWriter> Write_ListOf_ISomeItem = ( + WriteList( + WriteIClass)); + + private static readonly ContentWriter> Write_ListOf_ISpecialItem = ( + WriteList( + WriteIClass)); + + private static readonly ContentWriter Write_IContainer = ( + ContainerToSequence); + + private static void SomeItemToSequence( + Our.ISomeItem that, + Xml.XmlWriter writer) + { + WriteProperty( + "name", "Name", that.Name, writer, Write_string); + + if (that.Nickname != null) + { + WriteProperty( + "nickname", "Nickname", that.Nickname, writer, Write_string); + } + } // private static void SomeItemToSequence + + public override void VisitSomeItem( + Our.ISomeItem that, + Xml.XmlWriter writer) + { + writer.WriteStartElement( + "someItem", + NS); + SomeItemToSequence( + that, + writer); + writer.WriteEndElement(); + } + + private static void SpecialItemToSequence( + Our.ISpecialItem that, + Xml.XmlWriter writer) + { + WriteProperty( + "name", "Name", that.Name, writer, Write_string); + + if (that.Nickname != null) + { + WriteProperty( + "nickname", "Nickname", that.Nickname, writer, Write_string); + } + + WriteProperty( + "level", "Level", that.Level, writer, Write_long); + } // private static void SpecialItemToSequence + + public override void VisitSpecialItem( + Our.ISpecialItem that, + Xml.XmlWriter writer) + { + writer.WriteStartElement( + "specialItem", + NS); + SpecialItemToSequence( + that, + writer); + writer.WriteEndElement(); + } + + private static void ContainerToSequence( + Our.IContainer that, + Xml.XmlWriter writer) + { + WriteProperty( + "items", "Items", that.Items, writer, Write_ListOf_IItem); + + WriteProperty( + "someItems", "SomeItems", that.SomeItems, writer, Write_ListOf_ISomeItem); + + if (that.SpecialItems != null) + { + WriteProperty( + "specialItems", + "SpecialItems", + that.SpecialItems, + writer, + Write_ListOf_ISpecialItem); + } + } // private static void ContainerToSequence + + public override void VisitContainer( + Our.IContainer that, + Xml.XmlWriter writer) + { + writer.WriteStartElement( + "container", + NS); + ContainerToSequence( + that, + writer); + writer.WriteEndElement(); + } + + private static void WrapperToSequence( + Our.IWrapper that, + Xml.XmlWriter writer) + { + WriteProperty( + "container", "Container", that.Container, writer, Write_IContainer); + } // private static void WrapperToSequence + + public override void VisitWrapper( + Our.IWrapper that, + Xml.XmlWriter writer) + { + writer.WriteStartElement( + "wrapper", + NS); + WrapperToSequence( + that, + writer); + writer.WriteEndElement(); + } + } // internal class VisitorWithWriter + + /// + /// Serialize instances of meta-model classes to XML. + /// + /// + /// Here is an example how to serialize an instance of IItem: + /// + /// var anInstance = new Our.IItem( + /// /* ... some constructor arguments ... */ + /// ); + /// var writer = new System.Xml.XmlWriter( /* some arguments */ ); + /// Serialize.To( + /// anInstance, + /// writer); + /// + /// + public static class Serialize + { + /// + /// Serialize an instance of the meta-model to XML. + /// + /// + /// Thrown when a value within instance can not be + /// represented in XML + /// + public static void To( + Our.IClass that, + Xml.XmlWriter writer) + { + try + { + VisitorWithWriter.WriteIClass( + that, writer); + } + catch (SerializationFailure failure) + { + throw new SerializationException( + Reporting.GenerateCSharpPath(failure.Error.PathSegments), + failure.Error.Cause); + } + } + } // public static class Serialize + } // public static class Xmlization +} // namespace dummy + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/csharp/expected/variance_of_containers/input/snippets/namespace.txt b/dev/test_data/main/csharp/expected/variance_of_containers/input/snippets/namespace.txt new file mode 100644 index 000000000..2995a4d0e --- /dev/null +++ b/dev/test_data/main/csharp/expected/variance_of_containers/input/snippets/namespace.txt @@ -0,0 +1 @@ +dummy \ No newline at end of file diff --git a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/common/common.go b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/common/common.go index aef908bbd..740dbbbf3 100644 --- a/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/aas_core_meta.v3/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/common/common.go b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/common/common.go index 384cf6ed6..3038f3a94 100644 --- a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/verification/verification.go index 1b6343cee..8e0dcd8cb 100644 --- a/dev/test_data/main/golang/expected/annotated_assignments/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/annotated_assignments/expected_output/verification/verification.go @@ -47,13 +47,15 @@ func (ve *VerificationError) PathString() string { } // Check an optional class declared with `None` and assigned in a loop. -func FirstChildAIsSmall( - parents []ourtypes.IParent, +// +// The items of the argument parents are of the type parameter ParentsT instead of ourtypes.IParent so that the slices of the descendants of ourtypes.IParent can be passed without a copy, as the slices are invariant in Go. +func FirstChildAIsSmall[ParentsT ourtypes.IParent]( + parents []ParentsT, ) bool { var found ourtypes.IChildA = nil for _, parent := range parents { if ourtypes.IsChildA(parent) { - found = parent.(ourtypes.IChildA) + found = any(parent).(ourtypes.IChildA) break } } @@ -89,9 +91,11 @@ func TextIsAllowedUnlessLong( } // Check a non-optional variable declared wider than its value. -func LastChildBIsNotZero( +// +// The items of the argument parents are of the type parameter ParentsT instead of ourtypes.IParent so that the slices of the descendants of ourtypes.IParent can be passed without a copy, as the slices are invariant in Go. +func LastChildBIsNotZero[ParentsT ourtypes.IParent]( parent ourtypes.IParent, - parents []ourtypes.IParent, + parents []ParentsT, ) bool { var last ourtypes.IParent = parent for _, other := range parents { diff --git a/dev/test_data/main/golang/expected/arithmetic/expected_output/common/common.go b/dev/test_data/main/golang/expected/arithmetic/expected_output/common/common.go index 3eed0b382..96220fbeb 100644 --- a/dev/test_data/main/golang/expected/arithmetic/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/arithmetic/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/assert_statements/expected_output/common/common.go b/dev/test_data/main/golang/expected/assert_statements/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/assert_statements/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/assert_statements/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/assignment_targets/expected_output/common/common.go b/dev/test_data/main/golang/expected/assignment_targets/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/assignment_targets/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/assignment_targets/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/common/common.go b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/common/common.go index e830e0e1d..9645e4de3 100644 --- a/dev/test_data/main/golang/expected/collections_in_returns/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/collections_in_returns/expected_output/common/common.go @@ -40,6 +40,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/common/common.go b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/constrained_primitives/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/constrained_primitives/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/common/common.go b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/custom_serialization_names/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/common/common.go b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/deep_class_hierarchy/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/empty_class/expected_output/common/common.go b/dev/test_data/main/golang/expected/empty_class/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/empty_class/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/empty_class/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/enum/expected_output/common/common.go b/dev/test_data/main/golang/expected/enum/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/enum/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/enum/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/float_only/expected_output/common/common.go b/dev/test_data/main/golang/expected/float_only/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/float_only/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/float_only/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/for_statements/expected_output/common/common.go b/dev/test_data/main/golang/expected/for_statements/expected_output/common/common.go index a93f333f5..2a89bdda6 100644 --- a/dev/test_data/main/golang/expected/for_statements/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/for_statements/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/if_statements/expected_output/common/common.go b/dev/test_data/main/golang/expected/if_statements/expected_output/common/common.go index a93f333f5..2a89bdda6 100644 --- a/dev/test_data/main/golang/expected/if_statements/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/if_statements/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/if_statements/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/if_statements/expected_output/verification/verification.go index e48ed1322..d49772daa 100644 --- a/dev/test_data/main/golang/expected/if_statements/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/if_statements/expected_output/verification/verification.go @@ -228,17 +228,19 @@ func NarrowingAfterTheOnlyCompletingBranch( } // Check the narrowing after the `continue` and the early return in a loop. -func ChildAsHaveTexts( - parents []ourtypes.IParent, +// +// The items of the argument parents are of the type parameter ParentsT instead of ourtypes.IParent so that the slices of the descendants of ourtypes.IParent can be passed without a copy, as the slices are invariant in Go. +func ChildAsHaveTexts[ParentsT ourtypes.IParent]( + parents []ParentsT, ) bool { for _, parent := range parents { if !ourtypes.IsChildA(parent) { continue } - if parent.(ourtypes.IChildA).OptionalText() == nil { + if any(parent).(ourtypes.IChildA).OptionalText() == nil { return false } - if ourcommon.LenStr(*parent.(ourtypes.IChildA).OptionalText()) < 1 { + if ourcommon.LenStr(*any(parent).(ourtypes.IChildA).OptionalText()) < 1 { return false } } @@ -246,8 +248,10 @@ func ChildAsHaveTexts( } // Check the narrowing after the `continue` in a loop with a `break`. -func TextsBeforeContainerAreShort( - parents []ourtypes.IParent, +// +// The items of the argument parents are of the type parameter ParentsT instead of ourtypes.IParent so that the slices of the descendants of ourtypes.IParent can be passed without a copy, as the slices are invariant in Go. +func TextsBeforeContainerAreShort[ParentsT ourtypes.IParent]( + parents []ParentsT, ) bool { total := int64(0) for _, parent := range parents { @@ -275,14 +279,16 @@ func TextOrDefaultIsShort( } // Check the narrowing of a variable to a class by the assigned value. -func LastChildAIsSmall( +// +// The items of the argument parents are of the type parameter ParentsT instead of ourtypes.IParent so that the slices of the descendants of ourtypes.IParent can be passed without a copy, as the slices are invariant in Go. +func LastChildAIsSmall[ParentsT ourtypes.IParent]( parent ourtypes.IParent, - parents []ourtypes.IParent, + parents []ParentsT, ) bool { last := parent for _, other := range parents { if ourtypes.IsChildA(other) { - last = other.(ourtypes.IChildA) + last = any(other).(ourtypes.IChildA) if last.(ourtypes.IChildA).AOnly() >= 1000 { return false } diff --git a/dev/test_data/main/golang/expected/implementation_specific/expected_output/common/common.go b/dev/test_data/main/golang/expected/implementation_specific/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/implementation_specific/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/implementation_specific/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/common/common.go b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/common/common.go index a93f333f5..2a89bdda6 100644 --- a/dev/test_data/main/golang/expected/int_length_interplay/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/int_length_interplay/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/common/common.go b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/common/common.go index 0a823533a..68ab58f50 100644 --- a/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/int_parsing_and_lstrip/expected_output/common/common.go @@ -40,6 +40,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/is_instance/expected_output/common/common.go b/dev/test_data/main/golang/expected/is_instance/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/is_instance/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/is_instance/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/json_types/expected_output/common/common.go b/dev/test_data/main/golang/expected/json_types/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/json_types/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/json_types/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/common/common.go b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/json_types_in_collections/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/common/common.go b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/json_types_in_invariants/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/common/common.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go index 227c6b0be..c9e62fd42 100644 --- a/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go +++ b/dev/test_data/main/golang/expected/list_of_classes/expected_output/verification/verification.go @@ -72,13 +72,15 @@ func SimplesWith( // // The appended items are narrowed down to [ourtypes.ISomeItem], while the list // holds [ourtypes.IAbstractItem]'s. -func SomeItemsOf( - items []ourtypes.IAbstractItem, +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IAbstractItem so that the slices of the descendants of ourtypes.IAbstractItem can be passed without a copy, as the slices are invariant in Go. +func SomeItemsOf[ItemsT ourtypes.IAbstractItem]( + items []ItemsT, ) []ourtypes.IAbstractItem { var result []ourtypes.IAbstractItem = []ourtypes.IAbstractItem{} for _, item := range items { if ourtypes.IsSomeItem(item) { - result = append(result, item.(ourtypes.ISomeItem)) + result = append(result, any(item).(ourtypes.ISomeItem)) } } return result @@ -91,7 +93,7 @@ func IsInSimples( simple ourtypes.ISimple, simples []ourtypes.ISimple, ) bool { - return ourcommon.SliceContains( + return ourcommon.SliceContainsInstance( simples, simple, ) @@ -109,7 +111,7 @@ func IsInLocalCopyOfSimples( for _, other := range simples { copied = append(copied, other) } - return ourcommon.SliceContains( + return ourcommon.SliceContainsInstance( copied, simple, ) diff --git a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/common/common.go b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/list_of_constrained_primitives/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/list_of_enums/expected_output/common/common.go b/dev/test_data/main/golang/expected/list_of_enums/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/list_of_enums/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/list_of_enums/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/common/common.go b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/list_of_primitives/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/list_of_primitives/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/method_references/expected_output/common/common.go b/dev/test_data/main/golang/expected/method_references/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/method_references/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/method_references/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/common/common.go b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/nested_any_in_all/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/nested_collections/expected_output/common/common.go b/dev/test_data/main/golang/expected/nested_collections/expected_output/common/common.go index e830e0e1d..9645e4de3 100644 --- a/dev/test_data/main/golang/expected/nested_collections/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/nested_collections/expected_output/common/common.go @@ -40,6 +40,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/optional_arguments/expected_output/common/common.go b/dev/test_data/main/golang/expected/optional_arguments/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/optional_arguments/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/optional_arguments/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/primitive_types/expected_output/common/common.go b/dev/test_data/main/golang/expected/primitive_types/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/primitive_types/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/primitive_types/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/common/common.go b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/common/common.go index b7b572a16..9cd0eae53 100644 --- a/dev/test_data/main/golang/expected/problematic_keywords/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/problematic_keywords/expected_output/common/common.go @@ -38,6 +38,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/sets/expected_output/common/common.go b/dev/test_data/main/golang/expected/sets/expected_output/common/common.go index c395e3d5e..b655a2582 100644 --- a/dev/test_data/main/golang/expected/sets/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/sets/expected_output/common/common.go @@ -40,6 +40,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/common/common.go b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/string_slicing_and_find/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/switch_statements/expected_output/common/common.go b/dev/test_data/main/golang/expected/switch_statements/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/switch_statements/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/switch_statements/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/common/common.go b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/common/common.go index 3eed0b382..96220fbeb 100644 --- a/dev/test_data/main/golang/expected/transpiled_methods/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/transpiled_methods/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/tuples/expected_output/common/common.go b/dev/test_data/main/golang/expected/tuples/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/tuples/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/tuples/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/unions/expected_output/common/common.go b/dev/test_data/main/golang/expected/unions/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/unions/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/unions/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/common.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/common.go new file mode 100644 index 000000000..740dbbbf3 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/common.go @@ -0,0 +1,350 @@ +// Package common provides common functions shared among the other packages. +package common + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "strings" + "unicode/utf8" +) + +func Concat( + parts ...string, +) string { + b := new(strings.Builder) + for _, part := range parts { + b.WriteString(part) + } + return b.String() +} + +// Check if the map contains the given key. +func MapContains[K comparable, V any](m map[K]V, k K) bool { + _, ok := m[k] + return ok +} + +// Check if the slice contains the given value. +// +// We can not use ``slices.Contains`` as it is available only from Go 1.21 on. +// We compare the values as ``any``'s, since the interfaces, such as our classes, +// satisfy the constraint ``comparable`` only from Go 1.20 on. +func SliceContains[T any](s []T, v T) bool { + for _, item := range s { + if any(item) == any(v) { + return true + } + } + return false +} + +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + +// Check if any of the elements satisfy the condition. +func Some[V any](condition func(V) bool, l []V) bool { + ok := false + for _, v := range l { + ok = ok || condition(v) + } + return ok +} + +// Check if all the elements satisfy the condition. +func All[V any](condition func(V) bool, l []V) bool { + for _, v := range l { + if !condition(v) { + return false + } + } + return true +} + +// Check if some of the elements in the given range satisfy the condition. +// +// The range is over the lengths (`int`) or over the integers (`int64`). +func SomeRange[T int | int64](condition func(T) bool, start T, end T) bool { + for i := start; i < end; i++ { + if condition(i) { + return true + } + } + return false +} + +// Check if all the elements in the given range satisfy the condition. +// +// The range is over the lengths (`int`) or over the integers (`int64`). +func AllRange[T int | int64](condition func(T) bool, start T, end T) bool { + for i := start; i < end; i++ { + if !condition(i) { + return false + } + } + return true +} + +// Copy the `value` to a new variable, and return the pointer to it. +// +// We represent the optional values which are not nilable, such as strings, +// numbers and enumeration literals, as pointers. Go does not allow to take +// the address of a literal or of an expression, such as `&"something"` +// or `&(x + 1)`, so we need a function to hold the value in a variable of its +// own. Moreover, taking the address of a variable directly, such as `&text`, +// would alias the variable, so that a later assignment to the variable would +// also change the value pointed to. +// +// Mind that Go infers `int` for an untyped integer constant, so you need to +// convert it explicitly, for example, `NewAndPointTo(int64(1))`. +func NewAndPointTo[T any](value T) *T { + return &value +} + +// Represent a fixed-size heterogeneous tuple of 1 item(s). +type Tuple1[T1 any] struct { + Item1 T1 +} + +// Count the items of the tuple. +func (tuple Tuple1[T1]) Len() int { + return 1 +} + +// Represent a fixed-size heterogeneous tuple of 2 item(s). +type Tuple2[T1 any, T2 any] struct { + Item1 T1 + Item2 T2 +} + +// Count the items of the tuple. +func (tuple Tuple2[T1, T2]) Len() int { + return 2 +} + +// Represent a fixed-size heterogeneous tuple of 3 item(s). +type Tuple3[T1 any, T2 any, T3 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 +} + +// Count the items of the tuple. +func (tuple Tuple3[T1, T2, T3]) Len() int { + return 3 +} + +// Represent a fixed-size heterogeneous tuple of 4 item(s). +type Tuple4[T1 any, T2 any, T3 any, T4 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 + Item4 T4 +} + +// Count the items of the tuple. +func (tuple Tuple4[T1, T2, T3, T4]) Len() int { + return 4 +} + +// Represent a fixed-size heterogeneous tuple of 5 item(s). +type Tuple5[T1 any, T2 any, T3 any, T4 any, T5 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 + Item4 T4 + Item5 T5 +} + +// Count the items of the tuple. +func (tuple Tuple5[T1, T2, T3, T4, T5]) Len() int { + return 5 +} + +// Represent a fixed-size heterogeneous tuple of 6 item(s). +type Tuple6[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 + Item4 T4 + Item5 T5 + Item6 T6 +} + +// Count the items of the tuple. +func (tuple Tuple6[T1, T2, T3, T4, T5, T6]) Len() int { + return 6 +} + +// Represent a fixed-size heterogeneous tuple of 7 item(s). +type Tuple7[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 + Item4 T4 + Item5 T5 + Item6 T6 + Item7 T7 +} + +// Count the items of the tuple. +func (tuple Tuple7[T1, T2, T3, T4, T5, T6, T7]) Len() int { + return 7 +} + +// Represent a fixed-size heterogeneous tuple of 8 item(s). +type Tuple8[T1 any, T2 any, T3 any, T4 any, T5 any, T6 any, T7 any, T8 any] struct { + Item1 T1 + Item2 T2 + Item3 T3 + Item4 T4 + Item5 T5 + Item6 T6 + Item7 T7 + Item8 T8 +} + +// Count the items of the tuple. +func (tuple Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Len() int { + return 8 +} + +// Resolve `position` in a string of `length` as Python does in slicing. +// +// A negative position counts from the end, and the positions out of range are +// clamped to the string. +func resolvePosition(position int64, length int) int { + if position < 0 { + position += int64(length) + if position < 0 { + return 0 + } + return int(position) + } + + if position > int64(length) { + return length + } + return int(position) +} + +// Compute the byte offset of the character at `position` in `text`. +// +// The `position` counts the characters (code points), and must not exceed +// the number of characters in `text`. +func byteOffsetOf(text string, position int) int { + count := 0 + for offset := range text { + if count == position { + return offset + } + count++ + } + return len(text) +} + +// Count the characters (code points) of `text`. +// +// We follow the Python implementation of `len`, since Python is the language of +// the meta-model specifications. Hence, we count the characters instead of +// the UTF-8 bytes as the native `len` does. +func LenStr(text string) int { + return utf8.RuneCountInString(text) +} + +// Slice `text` from `start` up to `end`, exclusive. +// +// We follow the Python implementation of slicing, since Python is +// the language of the meta-model specifications. Hence, the positions count +// the characters (code points), a negative position counts from the end, +// the positions out of range are clamped to the string, and the slice is empty +// if `start` is not before `end`. +func SliceStr(text string, start int64, end int64) string { + length := LenStr(text) + theStart := resolvePosition(start, length) + theEnd := resolvePosition(end, length) + + if theStart >= theEnd { + return "" + } + + return text[byteOffsetOf(text, theStart):byteOffsetOf(text, theEnd)] +} + +// Slice `text` from `start` up to its end. +// +// See [SliceStr] for the semantics. +func SliceStrFrom(text string, start int64) string { + theStart := resolvePosition(start, LenStr(text)) + return text[byteOffsetOf(text, theStart):] +} + +// Find the first `sub` in `text` from `start` on. +// +// Return the position of `sub` in `text`, or -1 if `sub` could not be found. +// +// We follow the Python implementation of `str.find`, since Python is +// the language of the meta-model specifications. Hence, the positions count +// the characters (code points), a negative `start` counts from the end, and +// a `start` beyond the end of `text` gives -1. +func FindStr(text string, sub string, start int64) int64 { + length := int64(LenStr(text)) + if start < 0 { + start += length + if start < 0 { + start = 0 + } + } + + if start > length { + return -1 + } + + startOffset := byteOffsetOf(text, int(start)) + index := strings.Index(text[startOffset:], sub) + if index == -1 { + return -1 + } + + return start + int64(LenStr(text[startOffset:startOffset+index])) +} + +// Check if any of the items of the set satisfy the condition. +// +// The set is represented as a map to empty structs. +func SomeKey[K comparable](condition func(K) bool, set map[K]struct{}) bool { + for k := range set { + if condition(k) { + return true + } + } + return false +} + +// Check if all the items of the set satisfy the condition. +// +// The set is represented as a map to empty structs. +func AllKeys[K comparable](condition func(K) bool, set map[K]struct{}) bool { + for k := range set { + if !condition(k) { + return false + } + } + return true +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/string_helpers_test/string_helpers_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/string_helpers_test/string_helpers_test.go new file mode 100644 index 000000000..a7cf7f4cc --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/common/string_helpers_test/string_helpers_test.go @@ -0,0 +1,347 @@ +// Test `len`, the slicing of strings and `find` as used in the transpiled code. +// +// The transpiled code follows the Python implementation, since Python is +// the language of the meta-model specifications. Hence, the lengths and +// the positions count the characters (code points). The expected values have +// been computed with Python, and all the SDKs test against the very same cases. +package common_string_helpers_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "testing" + ourcommon "github.com/dummy-works/dummy/common" +) + +func TestLenEmptyText(t *testing.T) { + got := ourcommon.LenStr("") + if got != 0 { + t.Errorf("Expected len('') to give 0 (empty text), but got %d", got) + } +} + +func TestLenAsciiText(t *testing.T) { + got := ourcommon.LenStr("abc") + if got != 3 { + t.Errorf("Expected len('abc') to give 3 (ASCII text), but got %d", got) + } +} + +func TestLenCharacterOfTwoUtf8Bytes(t *testing.T) { + got := ourcommon.LenStr("é-") + if got != 2 { + t.Errorf("Expected len('é-') to give 2 (character of two UTF-8 bytes), but got %d", got) + } +} + +func TestLenCharacterBeyondTheBasicMultilingualPlane(t *testing.T) { + got := ourcommon.LenStr("\U0001f600-") + if got != 2 { + t.Errorf("Expected len('\U0001f600-') to give 2 (character beyond the Basic Multilingual Plane), but got %d", got) + } +} + +func TestLenMixedCharacters(t *testing.T) { + got := ourcommon.LenStr("aé\U0001f600b\U0001f600") + if got != 5 { + t.Errorf("Expected len('aé\U0001f600b\U0001f600') to give 5 (mixed characters), but got %d", got) + } +} + +func TestSliceStartAndEnd(t *testing.T) { + got := ourcommon.SliceStr("abcde", 1, 3) + if got != "bc" { + t.Errorf("Expected 'abcde'[1:3] to give 'bc' (start and end), but got %q", got) + } +} + +func TestSliceNoStart(t *testing.T) { + got := ourcommon.SliceStr("abcde", 0, 2) + if got != "ab" { + t.Errorf("Expected 'abcde'[:2] to give 'ab' (no start), but got %q", got) + } +} + +func TestSliceNoEnd(t *testing.T) { + got := ourcommon.SliceStrFrom("abcde", 3) + if got != "de" { + t.Errorf("Expected 'abcde'[3:] to give 'de' (no end), but got %q", got) + } +} + +func TestSliceNeitherStartNorEnd(t *testing.T) { + got := ourcommon.SliceStrFrom("abcde", 0) + if got != "abcde" { + t.Errorf("Expected 'abcde'[:] to give 'abcde' (neither start nor end), but got %q", got) + } +} + +func TestSliceNegativeStartCountsFromTheEnd(t *testing.T) { + got := ourcommon.SliceStrFrom("abcde", -2) + if got != "de" { + t.Errorf("Expected 'abcde'[-2:] to give 'de' (negative start counts from the end), but got %q", got) + } +} + +func TestSliceNegativeEndCountsFromTheEnd(t *testing.T) { + got := ourcommon.SliceStr("abcde", 0, -2) + if got != "abc" { + t.Errorf("Expected 'abcde'[:-2] to give 'abc' (negative end counts from the end), but got %q", got) + } +} + +func TestSliceNegativeStartAndEnd(t *testing.T) { + got := ourcommon.SliceStr("abcde", -4, -1) + if got != "bcd" { + t.Errorf("Expected 'abcde'[-4:-1] to give 'bcd' (negative start and end), but got %q", got) + } +} + +func TestSliceNegativeStartBeforeTheBeginningIsClamped(t *testing.T) { + got := ourcommon.SliceStr("abcde", -10, 2) + if got != "ab" { + t.Errorf("Expected 'abcde'[-10:2] to give 'ab' (negative start before the beginning is clamped), but got %q", got) + } +} + +func TestSliceEndBeyondTheEndIsClamped(t *testing.T) { + got := ourcommon.SliceStr("abcde", 2, 10) + if got != "cde" { + t.Errorf("Expected 'abcde'[2:10] to give 'cde' (end beyond the end is clamped), but got %q", got) + } +} + +func TestSliceStartBeyondTheEndGivesEmpty(t *testing.T) { + got := ourcommon.SliceStrFrom("abcde", 10) + if got != "" { + t.Errorf("Expected 'abcde'[10:] to give '' (start beyond the end gives empty), but got %q", got) + } +} + +func TestSliceStartAfterEndGivesEmpty(t *testing.T) { + got := ourcommon.SliceStr("abcde", 3, 1) + if got != "" { + t.Errorf("Expected 'abcde'[3:1] to give '' (start after end gives empty), but got %q", got) + } +} + +func TestSliceNegativeStartAfterNegativeEndGivesEmpty(t *testing.T) { + got := ourcommon.SliceStr("abcde", -1, -3) + if got != "" { + t.Errorf("Expected 'abcde'[-1:-3] to give '' (negative start after negative end gives empty), but got %q", got) + } +} + +func TestSliceStartEqualToEndGivesEmpty(t *testing.T) { + got := ourcommon.SliceStr("abcde", 5, 5) + if got != "" { + t.Errorf("Expected 'abcde'[5:5] to give '' (start equal to end gives empty), but got %q", got) + } +} + +func TestSliceEmptyText(t *testing.T) { + got := ourcommon.SliceStr("", 0, 0) + if got != "" { + t.Errorf("Expected ''[0:0] to give '' (empty text), but got %q", got) + } +} + +func TestSliceNegativeStartOnEmptyText(t *testing.T) { + got := ourcommon.SliceStrFrom("", -1) + if got != "" { + t.Errorf("Expected ''[-1:] to give '' (negative start on empty text), but got %q", got) + } +} + +func TestSliceEndBeyondTheEndOfEmptyText(t *testing.T) { + got := ourcommon.SliceStr("", 0, 5) + if got != "" { + t.Errorf("Expected ''[:5] to give '' (end beyond the end of empty text), but got %q", got) + } +} + +func TestSliceAfterACharacterOfTwoUtf8Bytes(t *testing.T) { + got := ourcommon.SliceStrFrom("é-x", 1) + if got != "-x" { + t.Errorf("Expected 'é-x'[1:] to give '-x' (after a character of two UTF-8 bytes), but got %q", got) + } +} + +func TestSliceCharacterOfTwoUtf8Bytes(t *testing.T) { + got := ourcommon.SliceStr("aéb", 1, 2) + if got != "é" { + t.Errorf("Expected 'aéb'[1:2] to give 'é' (character of two UTF-8 bytes), but got %q", got) + } +} + +func TestSliceCharacterBeyondTheBasicMultilingualPlane(t *testing.T) { + got := ourcommon.SliceStr("a\U0001f600b", 1, 2) + if got != "\U0001f600" { + t.Errorf("Expected 'a\U0001f600b'[1:2] to give '\U0001f600' (character beyond the Basic Multilingual Plane), but got %q", got) + } +} + +func TestSliceAfterACharacterBeyondTheBasicMultilingualPlane(t *testing.T) { + got := ourcommon.SliceStrFrom("\U0001f600-x", 1) + if got != "-x" { + t.Errorf("Expected '\U0001f600-x'[1:] to give '-x' (after a character beyond the Basic Multilingual Plane), but got %q", got) + } +} + +func TestSliceNegativeStartOnCharactersBeyondAscii(t *testing.T) { + got := ourcommon.SliceStr("aé\U0001f600b\U0001f600", -3, -1) + if got != "\U0001f600b" { + t.Errorf("Expected 'aé\U0001f600b\U0001f600'[-3:-1] to give '\U0001f600b' (negative start on characters beyond ASCII), but got %q", got) + } +} + +func TestFindFound(t *testing.T) { + got := ourcommon.FindStr("abcabc", "b", 0) + if got != 1 { + t.Errorf("Expected 'abcabc'.find('b') to give 1 (found), but got %d", got) + } +} + +func TestFindNotFound(t *testing.T) { + got := ourcommon.FindStr("abcabc", "x", 0) + if got != -1 { + t.Errorf("Expected 'abcabc'.find('x') to give -1 (not found), but got %d", got) + } +} + +func TestFindEmptySub(t *testing.T) { + got := ourcommon.FindStr("abcabc", "", 0) + if got != 0 { + t.Errorf("Expected 'abcabc'.find('') to give 0 (empty sub), but got %d", got) + } +} + +func TestFindSubLongerThanText(t *testing.T) { + got := ourcommon.FindStr("ab", "abc", 0) + if got != -1 { + t.Errorf("Expected 'ab'.find('abc') to give -1 (sub longer than text), but got %d", got) + } +} + +func TestFindFoundAfterStart(t *testing.T) { + got := ourcommon.FindStr("abcabc", "b", 2) + if got != 4 { + t.Errorf("Expected 'abcabc'.find('b', 2) to give 4 (found after start), but got %d", got) + } +} + +func TestFindFoundOnlyBeforeStart(t *testing.T) { + got := ourcommon.FindStr("abcabc", "b", 5) + if got != -1 { + t.Errorf("Expected 'abcabc'.find('b', 5) to give -1 (found only before start), but got %d", got) + } +} + +func TestFindNegativeStartCountsFromTheEnd(t *testing.T) { + got := ourcommon.FindStr("abcabc", "c", -1) + if got != 5 { + t.Errorf("Expected 'abcabc'.find('c', -1) to give 5 (negative start counts from the end), but got %d", got) + } +} + +func TestFindNegativeStartFindsTheLaterOccurrence(t *testing.T) { + got := ourcommon.FindStr("abcabc", "a", -3) + if got != 3 { + t.Errorf("Expected 'abcabc'.find('a', -3) to give 3 (negative start finds the later occurrence), but got %d", got) + } +} + +func TestFindNegativeStartBeforeTheBeginningIsClamped(t *testing.T) { + got := ourcommon.FindStr("abcabc", "a", -10) + if got != 0 { + t.Errorf("Expected 'abcabc'.find('a', -10) to give 0 (negative start before the beginning is clamped), but got %d", got) + } +} + +func TestFindEmptySubAtTheEnd(t *testing.T) { + got := ourcommon.FindStr("abcabc", "", 6) + if got != 6 { + t.Errorf("Expected 'abcabc'.find('', 6) to give 6 (empty sub at the end), but got %d", got) + } +} + +func TestFindEmptySubBeyondTheEndGives1(t *testing.T) { + got := ourcommon.FindStr("abcabc", "", 7) + if got != -1 { + t.Errorf("Expected 'abcabc'.find('', 7) to give -1 (empty sub beyond the end gives -1), but got %d", got) + } +} + +func TestFindStartBeyondTheEndGives1(t *testing.T) { + got := ourcommon.FindStr("abcabc", "c", 10) + if got != -1 { + t.Errorf("Expected 'abcabc'.find('c', 10) to give -1 (start beyond the end gives -1), but got %d", got) + } +} + +func TestFindEmptySubInEmptyText(t *testing.T) { + got := ourcommon.FindStr("", "", 0) + if got != 0 { + t.Errorf("Expected ''.find('') to give 0 (empty sub in empty text), but got %d", got) + } +} + +func TestFindEmptySubBeyondTheEndOfEmptyTextGives1(t *testing.T) { + got := ourcommon.FindStr("", "", 1) + if got != -1 { + t.Errorf("Expected ''.find('', 1) to give -1 (empty sub beyond the end of empty text gives -1), but got %d", got) + } +} + +func TestFindNotFoundInEmptyText(t *testing.T) { + got := ourcommon.FindStr("", "x", 0) + if got != -1 { + t.Errorf("Expected ''.find('x') to give -1 (not found in empty text), but got %d", got) + } +} + +func TestFindAfterACharacterOfTwoUtf8Bytes(t *testing.T) { + got := ourcommon.FindStr("é-", "-", 0) + if got != 1 { + t.Errorf("Expected 'é-'.find('-') to give 1 (after a character of two UTF-8 bytes), but got %d", got) + } +} + +func TestFindAfterACharacterBeyondTheBasicMultilingualPlane(t *testing.T) { + got := ourcommon.FindStr("\U0001f600-", "-", 0) + if got != 1 { + t.Errorf("Expected '\U0001f600-'.find('-') to give 1 (after a character beyond the Basic Multilingual Plane), but got %d", got) + } +} + +func TestFindCharacterBeyondTheBasicMultilingualPlane(t *testing.T) { + got := ourcommon.FindStr("a\U0001f600b", "\U0001f600", 0) + if got != 1 { + t.Errorf("Expected 'a\U0001f600b'.find('\U0001f600') to give 1 (character beyond the Basic Multilingual Plane), but got %d", got) + } +} + +func TestFindStartAfterACharacterBeyondAscii(t *testing.T) { + got := ourcommon.FindStr("\U0001f600a\U0001f600a", "a", 2) + if got != 3 { + t.Errorf("Expected '\U0001f600a\U0001f600a'.find('a', 2) to give 3 (start after a character beyond ASCII), but got %d", got) + } +} + +func TestFindNegativeStartOnCharactersBeyondAscii(t *testing.T) { + got := ourcommon.FindStr("\U0001f600a\U0001f600a", "\U0001f600", -2) + if got != 2 { + t.Errorf("Expected '\U0001f600a\U0001f600a'.find('\U0001f600', -2) to give 2 (negative start on characters beyond ASCII), but got %d", got) + } +} + +func TestFindStartBeyondTheEndOfCharactersBeyondAscii(t *testing.T) { + got := ourcommon.FindStr("é\U0001f600", "", 3) + if got != -1 { + t.Errorf("Expected 'é\U0001f600'.find('', 3) to give -1 (start beyond the end of characters beyond ASCII), but got %d", got) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/constants/constants.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/constants/constants.go new file mode 100644 index 000000000..7b3022602 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/constants/constants.go @@ -0,0 +1,11 @@ +// Package constants provides immutable values of the meta-model. +// +// These are not necessarily constants in the sense of Go language, but +// variables that are expected not to be mutated by the clients. +package constants + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/enhancing.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/enhancing.go new file mode 100644 index 000000000..9f4b9a4c1 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/enhancing.go @@ -0,0 +1,478 @@ +// Package enhancing allows for enhancement of model instances with your custom data. +package enhancing + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + ourtypes "github.com/dummy-works/dummy/types" +) + +type enhanced[E any] interface { + // Get the enhancement from the enhanced instance. + getEnhancement() E + + // Set the enhancement of the enhanced instance. + setEnhancement(E) +} + +type enhancedSomeItem[E any] struct { + instance ourtypes.ISomeItem + enhancement E +} + +func (esi *enhancedSomeItem[E]) ModelType( +) ourtypes.ModelType { + return esi.instance.ModelType() +} + +func (esi *enhancedSomeItem[E]) DescendOnce( + action func(ourtypes.IClass)bool, +) bool { + return esi.instance.DescendOnce(action) +} + +func (esi *enhancedSomeItem[E]) Descend( + action func(ourtypes.IClass) bool, +) bool { + return esi.instance.Descend(action) +} + +func (esi *enhancedSomeItem[E]) Name( +) string { + return esi.instance.Name() +} + +func (esi *enhancedSomeItem[E]) SetName( + value string, +) { + esi.instance.SetName(value) +} + +func (esi *enhancedSomeItem[E]) Nickname( +) *string { + return esi.instance.Nickname() +} + +func (esi *enhancedSomeItem[E]) SetNickname( + value *string, +) { + esi.instance.SetNickname(value) +} + +func (esi *enhancedSomeItem[E]) getEnhancement( +) E { + return esi.enhancement +} + +func (esi *enhancedSomeItem[E]) setEnhancement( + value E, +) { + esi.enhancement = value +} + +func wrapSomeItem[E any]( + that ourtypes.ISomeItem, + factory func(ourtypes.IClass) (E, bool), +) (result ourtypes.ISomeItem) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + enh, shouldEnhance := factory(that) + if shouldEnhance { + result = &enhancedSomeItem[E]{ + instance: that, + enhancement: enh, + } + } else { + result = that + } + + return +} + +type enhancedSpecialItem[E any] struct { + instance ourtypes.ISpecialItem + enhancement E +} + +func (esi *enhancedSpecialItem[E]) ModelType( +) ourtypes.ModelType { + return esi.instance.ModelType() +} + +func (esi *enhancedSpecialItem[E]) DescendOnce( + action func(ourtypes.IClass)bool, +) bool { + return esi.instance.DescendOnce(action) +} + +func (esi *enhancedSpecialItem[E]) Descend( + action func(ourtypes.IClass) bool, +) bool { + return esi.instance.Descend(action) +} + +func (esi *enhancedSpecialItem[E]) Name( +) string { + return esi.instance.Name() +} + +func (esi *enhancedSpecialItem[E]) SetName( + value string, +) { + esi.instance.SetName(value) +} + +func (esi *enhancedSpecialItem[E]) Nickname( +) *string { + return esi.instance.Nickname() +} + +func (esi *enhancedSpecialItem[E]) SetNickname( + value *string, +) { + esi.instance.SetNickname(value) +} + +func (esi *enhancedSpecialItem[E]) Level( +) int64 { + return esi.instance.Level() +} + +func (esi *enhancedSpecialItem[E]) SetLevel( + value int64, +) { + esi.instance.SetLevel(value) +} + +func (esi *enhancedSpecialItem[E]) getEnhancement( +) E { + return esi.enhancement +} + +func (esi *enhancedSpecialItem[E]) setEnhancement( + value E, +) { + esi.enhancement = value +} + +func wrapSpecialItem[E any]( + that ourtypes.ISpecialItem, + factory func(ourtypes.IClass) (E, bool), +) (result ourtypes.ISpecialItem) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + enh, shouldEnhance := factory(that) + if shouldEnhance { + result = &enhancedSpecialItem[E]{ + instance: that, + enhancement: enh, + } + } else { + result = that + } + + return +} + +type enhancedContainer[E any] struct { + instance ourtypes.IContainer + enhancement E +} + +func (ec *enhancedContainer[E]) ModelType( +) ourtypes.ModelType { + return ec.instance.ModelType() +} + +func (ec *enhancedContainer[E]) DescendOnce( + action func(ourtypes.IClass)bool, +) bool { + return ec.instance.DescendOnce(action) +} + +func (ec *enhancedContainer[E]) Descend( + action func(ourtypes.IClass) bool, +) bool { + return ec.instance.Descend(action) +} + +func (ec *enhancedContainer[E]) Items( +) []ourtypes.IItem { + return ec.instance.Items() +} + +func (ec *enhancedContainer[E]) SetItems( + value []ourtypes.IItem, +) { + ec.instance.SetItems(value) +} + +func (ec *enhancedContainer[E]) SomeItems( +) []ourtypes.ISomeItem { + return ec.instance.SomeItems() +} + +func (ec *enhancedContainer[E]) SetSomeItems( + value []ourtypes.ISomeItem, +) { + ec.instance.SetSomeItems(value) +} + +func (ec *enhancedContainer[E]) SpecialItems( +) []ourtypes.ISpecialItem { + return ec.instance.SpecialItems() +} + +func (ec *enhancedContainer[E]) SetSpecialItems( + value []ourtypes.ISpecialItem, +) { + ec.instance.SetSpecialItems(value) +} + +func (c *enhancedContainer[E]) CountItems( + items []ourtypes.IItem, +) int64 { + return c.instance.CountItems( + items, + ) +} + +func (ec *enhancedContainer[E]) getEnhancement( +) E { + return ec.enhancement +} + +func (ec *enhancedContainer[E]) setEnhancement( + value E, +) { + ec.enhancement = value +} + +func wrapContainer[E any]( + that ourtypes.IContainer, + factory func(ourtypes.IClass) (E, bool), +) (result ourtypes.IContainer) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + enh, shouldEnhance := factory(that) + if shouldEnhance { + result = &enhancedContainer[E]{ + instance: that, + enhancement: enh, + } + } else { + result = that + } + + wrap_ListOf_IItem_inPlace[E](that.Items(), factory) + + wrap_ListOf_ISomeItem_inPlace[E](that.SomeItems(), factory) + + theSpecialItems := that.SpecialItems() + if theSpecialItems != nil { + wrap_ListOf_ISpecialItem_inPlace[E](theSpecialItems, factory) + } + + return +} + +type enhancedWrapper[E any] struct { + instance ourtypes.IWrapper + enhancement E +} + +func (ew *enhancedWrapper[E]) ModelType( +) ourtypes.ModelType { + return ew.instance.ModelType() +} + +func (ew *enhancedWrapper[E]) DescendOnce( + action func(ourtypes.IClass)bool, +) bool { + return ew.instance.DescendOnce(action) +} + +func (ew *enhancedWrapper[E]) Descend( + action func(ourtypes.IClass) bool, +) bool { + return ew.instance.Descend(action) +} + +func (ew *enhancedWrapper[E]) Container( +) ourtypes.IContainer { + return ew.instance.Container() +} + +func (ew *enhancedWrapper[E]) SetContainer( + value ourtypes.IContainer, +) { + ew.instance.SetContainer(value) +} + +func (ew *enhancedWrapper[E]) getEnhancement( +) E { + return ew.enhancement +} + +func (ew *enhancedWrapper[E]) setEnhancement( + value E, +) { + ew.enhancement = value +} + +func wrapWrapper[E any]( + that ourtypes.IWrapper, + factory func(ourtypes.IClass) (E, bool), +) (result ourtypes.IWrapper) { + // We assume that we already checked whether `that` has been enhanced + // in the caller. + + enh, shouldEnhance := factory(that) + if shouldEnhance { + result = &enhancedWrapper[E]{ + instance: that, + enhancement: enh, + } + } else { + result = that + } + + that.SetContainer( + wrapClass[E](that.Container(), factory), + ) + + return +} + +// Wrap `that` instance recursively with the enhancement produced by +// the `factory`, and keep its static type. +func wrapClass[E any, T ourtypes.IClass]( + that T, + factory func(ourtypes.IClass) (E, bool), +) T { + return Wrap[E](that, factory).(T) +} + +// Wrap recursively the instances held by `that` in-situ. +func wrap_ListOf_IItem_inPlace[E any]( + that []ourtypes.IItem, + factory func(ourtypes.IClass) (E, bool), +) { + for i := range that { + that[i] = wrapClass[E](that[i], factory) + } +} + +// Wrap recursively the instances held by `that` in-situ. +func wrap_ListOf_ISomeItem_inPlace[E any]( + that []ourtypes.ISomeItem, + factory func(ourtypes.IClass) (E, bool), +) { + for i := range that { + that[i] = wrapClass[E](that[i], factory) + } +} + +// Wrap recursively the instances held by `that` in-situ. +func wrap_ListOf_ISpecialItem_inPlace[E any]( + that []ourtypes.ISpecialItem, + factory func(ourtypes.IClass) (E, bool), +) { + for i := range that { + that[i] = wrapClass[E](that[i], factory) + } +} + +// Wrap `that` instance recursively with the enhancement produced by the `factory`. +// +// The factory returns the enhancement, and a boolean "should-enhance". If +// the "should-enhance" is false, `that` instance is not enhance, and we simply +// return it. However, we will still continue to enhance the instances referenced +// by `that` instance recursively. +// +// If `that` instance has been already wrapped, panic. +func Wrap[E any]( + that ourtypes.IClass, + factory func(ourtypes.IClass) (E, bool), +) (result ourtypes.IClass) { + _, ok := that.(enhanced[E]) + if ok { + panic( + fmt.Sprintf( + "An instance of %T has been already wrapped: %v", + that, that, + ), + ) + } + + switch that.ModelType() { + case ourtypes.ModelTypeSomeItem: + result = wrapSomeItem[E]( + that.(ourtypes.ISomeItem), + factory, + ) + case ourtypes.ModelTypeSpecialItem: + result = wrapSpecialItem[E]( + that.(ourtypes.ISpecialItem), + factory, + ) + case ourtypes.ModelTypeContainer: + result = wrapContainer[E]( + that.(ourtypes.IContainer), + factory, + ) + case ourtypes.ModelTypeWrapper: + result = wrapWrapper[E]( + that.(ourtypes.IWrapper), + factory, + ) + default: + panic( + fmt.Sprintf( + "Unexpected model type: %v", + that.ModelType(), + ), + ) + } + + return +} + +// Retrieve the enhancement from `that` instance. +// +// Return the enhancement, or `ok` false, if `that` instance has not been +// enhanced. +func Unwrap[E any](that ourtypes.IClass) (enhancement E, ok bool) { + var enh enhanced[E] + enh, ok = that.(enhanced[E]) + if !ok { + return + } + enhancement = enh.getEnhancement() + return +} + +// Retrieve the enhancement from `that` instance. +// +// If `that` instance has not been enhanced yet, panic. +func MustUnwrap[E any](that ourtypes.IClass) (enhancement E) { + var ok bool + enhancement, ok = Unwrap[E](that) + if !ok { + panic( + fmt.Sprintf( + "An instance of %T has not been wrapped: %v", + that, that, + ), + ) + } + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/test/enhancing_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/test/enhancing_test.go new file mode 100644 index 000000000..074966dec --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/enhancing/test/enhancing_test.go @@ -0,0 +1,295 @@ +package enhancing_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "testing" + ourenhancing "github.com/dummy-works/dummy/enhancing" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" +) + +type Enhancement struct { + ID int +} + +func collectIDsAndAssertTheyAreConsecutiveAndTheirCountEqualsNextID( + t *testing.T, + wrapped ourtypes.IClass, + nextID int, +) { + var ids []int + + instanceEnh := ourenhancing.MustUnwrap[*Enhancement](wrapped) + ids = append(ids, instanceEnh.ID) + + wrapped.Descend( + func(that ourtypes.IClass) (abort bool) { + enh := ourenhancing.MustUnwrap[*Enhancement](that) + ids = append(ids, enh.ID) + return + }, + ) + + if len(ids) != nextID { + t.Fatalf("Expected to collect %d IDs, but got: %d", len(ids), nextID) + return + } + + for i, id := range ids { + if id != i { + t.Fatalf( + "Unexpected ID at index %d (starting from 0); expected %d, got %d", + i, i, id, + ) + } + } +} + +func TestSomeItemWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalSomeItem() + + nextID := 0 + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + enh = &Enhancement{} + enh.ID = nextID + should = true + + nextID++ + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + collectIDsAndAssertTheyAreConsecutiveAndTheirCountEqualsNextID( + t, wrapped, nextID, + ) +} + +func TestSomeItemNothingWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalSomeItem() + + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + should = false + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + // Wrapped should be equal to instance by reference as our enhancement factory + // did not wrap anything. + if wrapped != instance { + t.Fatalf("Unexpected inequality between %v and %v", wrapped, instance) + } + + wrapped.Descend(func (that ourtypes.IClass) (abort bool) { + _, ok := ourenhancing.Unwrap[*Enhancement](that) + if ok { + t.Fatalf("Unexpected wrapped descendant: %v", that) + } + return + }) +} + +func TestSpecialItemWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalSpecialItem() + + nextID := 0 + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + enh = &Enhancement{} + enh.ID = nextID + should = true + + nextID++ + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + collectIDsAndAssertTheyAreConsecutiveAndTheirCountEqualsNextID( + t, wrapped, nextID, + ) +} + +func TestSpecialItemNothingWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalSpecialItem() + + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + should = false + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + // Wrapped should be equal to instance by reference as our enhancement factory + // did not wrap anything. + if wrapped != instance { + t.Fatalf("Unexpected inequality between %v and %v", wrapped, instance) + } + + wrapped.Descend(func (that ourtypes.IClass) (abort bool) { + _, ok := ourenhancing.Unwrap[*Enhancement](that) + if ok { + t.Fatalf("Unexpected wrapped descendant: %v", that) + } + return + }) +} + +func TestContainerWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalContainer() + + nextID := 0 + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + enh = &Enhancement{} + enh.ID = nextID + should = true + + nextID++ + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + collectIDsAndAssertTheyAreConsecutiveAndTheirCountEqualsNextID( + t, wrapped, nextID, + ) +} + +func TestContainerNothingWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalContainer() + + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + should = false + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + // Wrapped should be equal to instance by reference as our enhancement factory + // did not wrap anything. + if wrapped != instance { + t.Fatalf("Unexpected inequality between %v and %v", wrapped, instance) + } + + wrapped.Descend(func (that ourtypes.IClass) (abort bool) { + _, ok := ourenhancing.Unwrap[*Enhancement](that) + if ok { + t.Fatalf("Unexpected wrapped descendant: %v", that) + } + return + }) +} + +func TestWrapperWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalWrapper() + + nextID := 0 + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + enh = &Enhancement{} + enh.ID = nextID + should = true + + nextID++ + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + collectIDsAndAssertTheyAreConsecutiveAndTheirCountEqualsNextID( + t, wrapped, nextID, + ) +} + +func TestWrapperNothingWrapped(t *testing.T) { + instance := ourtesting.MustLoadMaximalWrapper() + + wrapped := ourenhancing.Wrap[*Enhancement]( + instance, + func(that ourtypes.IClass) (enh *Enhancement, should bool) { + should = false + return + }, + ) + + if !ourtesting.DeepEqual(instance, wrapped) { + t.Fatalf( + "Deep equality failed between the instance and the wrapped: %v %v", + instance, wrapped, + ) + } + + // Wrapped should be equal to instance by reference as our enhancement factory + // did not wrap anything. + if wrapped != instance { + t.Fatalf("Unexpected inequality between %v and %v", wrapped, instance) + } + + wrapped.Descend(func (that ourtypes.IClass) (abort bool) { + _, ok := ourenhancing.Unwrap[*Enhancement](that) + if ok { + t.Fatalf("Unexpected wrapped descendant: %v", that) + } + return + }) +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/internal/xmlcommon/xmlcommon.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/internal/xmlcommon/xmlcommon.go new file mode 100644 index 000000000..436dbd116 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/internal/xmlcommon/xmlcommon.go @@ -0,0 +1,1062 @@ +// Package xmlcommon provides the XML primitives shared by the de/serialization. +// +// The xmlization reads and writes the elements of our own classes, and +// the xmlrpc package the elements within a JSON-able value. The whitespace +// skipping, the namespace checks and the framing of an element are the same +// for both, and live here. +package xmlcommon + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + b64 "encoding/base64" + "encoding/xml" + "errors" + "fmt" + "io" + "math" + "regexp" + "strconv" + "strings" + "unicode" + ourcommon "github.com/dummy-works/dummy/common" + ourreporting "github.com/dummy-works/dummy/reporting" +) + +// Namespace is the XML namespace in which all the elements of a document live. +// +// The elements of the XML-RPC subset, over which a JSON-able value is +// de/serialized, are the one exception: they live in no namespace at all. +const Namespace = "https://dummy.com" + +// Represent an error during the de-serialization. +// +// Implements `error`. +type DeserializationError struct{ + Path *ourreporting.Path + Message string +} + +func NewDeserializationError(message string) *DeserializationError { + return &DeserializationError{ + Path: &ourreporting.Path{}, + Message: message, + } +} + +func (de *DeserializationError) Error() string { + return fmt.Sprintf( + "%s: %s", + de.PathString(), + de.Message, + ) +} + +// Render the path as a string. +func (de *DeserializationError) PathString() string { + return ourreporting.ToRelativeXPath(de.Path) +} + +// Prepend the element with the `name` to the path, and return the error back +// for chaining. +func (de *DeserializationError) PrependName( + name string, +) *DeserializationError { + de.Path.PrependName( + &ourreporting.NameSegment{Name: name}, + ) + return de +} + +// Prepend the element at the `index` to the path, and return the error back +// for chaining. +func (de *DeserializationError) PrependIndex( + index int, +) *DeserializationError { + de.Path.PrependIndex( + &ourreporting.IndexSegment{Index: index}, + ) + return de +} + +// Prepend the member with the `key` to the path, and return the error back +// for chaining. +func (de *DeserializationError) PrependKey( + key string, +) *DeserializationError { + de.Path.PrependKey( + &ourreporting.KeySegment{Key: key}, + ) + return de +} + +// Cast `err` to a de-serialization error, or panic. +// +// Every error which the de-serialization raises is +// a [DeserializationError], so the cast can only fail on an error which came +// from somewhere else entirely. +func MustDeserializationError(err error) *DeserializationError { + deseriaErr, ok := err.(*DeserializationError) + if !ok { + panic( + fmt.Sprintf( + "Expected a *DeserializationError, but got %T: %v", + err, err, + ), + ) + } + return deseriaErr +} + +// Represent an error during the serialization. +// +// Implements `error`. +type SerializationError struct { + Path *ourreporting.Path + Message string +} + +func NewSerializationError(message string) *SerializationError { + return &SerializationError{ + Path: &ourreporting.Path{}, + Message: message, + } +} + +func (se *SerializationError) Error() string { + return fmt.Sprintf( + "%s: %s", + se.PathString(), + se.Message, + ) +} + +// Render the path as a string. +func (se *SerializationError) PathString() string { + return ourreporting.ToGolangPath(se.Path) +} + +// Prepend the item at the `index` to the path, and return the error back +// for chaining. +func (se *SerializationError) PrependIndex( + index int, +) *SerializationError { + se.Path.PrependIndex( + &ourreporting.IndexSegment{Index: index}, + ) + return se +} + +// Prepend the member with the `key` to the path, and return the error back +// for chaining. +func (se *SerializationError) PrependKey( + key string, +) *SerializationError { + se.Path.PrependKey( + &ourreporting.KeySegment{Key: key}, + ) + return se +} + +// Cast `err` to a serialization error, or panic. +// +// See the note on [MustDeserializationError]. +func MustSerializationError(err error) *SerializationError { + seriaErr, ok := err.(*SerializationError) + if !ok { + panic( + fmt.Sprintf( + "Expected a *SerializationError, but got %T: %v", + err, err, + ), + ) + } + return seriaErr +} + +// Represent a sentinel token which signals the end-of-file. +type Eof struct{} + +// Check if the string `s` consists only of whitespace. +// +// An empty string causes panic — please cover that case before. +func IsWhitespace(s string) bool { + if len(s) == 0 { + panic("Unexpected empty string") + } + for _, c := range s { + if !unicode.IsSpace(c) { + return false + } + } + return true +} + +// Read the next token from the `decoder` given the `current` token. +// +// If `current` token is [Eof], return [Eof]. +func ReadNext(decoder *xml.Decoder, current xml.Token) (next xml.Token, err error) { + if _, isEOF := current.(Eof); isEOF { + next = current + return + } + + var tokenErr error + next, tokenErr = decoder.Token() + if tokenErr != nil { + if tokenErr == io.EOF { + next = &Eof{} + return + } + + err = tokenErr + return + } + + return +} + +// Read all the possible whitespace and comments. +// +// Return the `next` token which is neither empty text, nor whitespace nor comment, +// or [Eof], if we reached the end-of-file. +// +// If we already reached the end-of-file, simply return [Eof]. +func SkipEmptyTextWhitespaceAndComments( + decoder *xml.Decoder, + current xml.Token, +) (next xml.Token, err error) { + stop := false + for !stop { + if _, isEOF := current.(Eof); isEOF { + break + } + + switch et := current.(type) { + case xml.CharData: + text := string(et) + if len(text) != 0 && !IsWhitespace(text) { + stop = true + } else { + // We should proceed to the next token. + } + case xml.Comment: + // We should proceed to the next token. + default: + stop = true + } + + if !stop { + current, err = ReadNext(decoder, current) + if err != nil { + return + } + } + } + + next = current + return +} + +// Consume the text tokens (char data). +// +// Any comment tokens are skipped. +// +// Match a run of the four characters which XML calls whitespace. +var whitespaceRunRe = regexp.MustCompile("[ \t\n\r]+") + +// Normalize `text` the way `whiteSpace="collapse"` prescribes. +// +// Every atomic XSD type except a string, and every type derived from one by +// restriction, fixes `whiteSpace` to `collapse`, and a schema author can not +// change it. A tab, a line feed and a carriage return each become a space, +// a run of spaces becomes one space, and the leading and trailing spaces go. +// Only the result of that is a lexical representation to be matched. +// +// Mind that this strips only the whitespace *around* the value: a space +// within it survives as a single space, so "2 3" becomes "2 3", which is +// still no number. +// +// See: https://www.w3.org/TR/xmlschema-2/#rf-whiteSpace +func CollapseWhitespace(text string) string { + return strings.Trim(whitespaceRunRe.ReplaceAllString(text, " "), " ") +} + +// Tell whether `text` is a lexical form of `xs:base64Binary`. +// +// The whitespace is expected to be gone already. What is left has to match +// `(B64 B64 B64 B64)* ((B64 B64 B64 B64) | (B64 B64 B16 "=") | (B64 B04 "=="))?` +// -- a length which is a multiple of four, the alphabet and nothing else, +// an equals sign only at the very end, and, easily missed, a constrained +// character *before* the padding, as the bits which the padding drops have to +// be zero. +// +// The decoders do not agree on any of this, so every target does the same +// check of its own and refuses the same texts. +// +// See: https://www.w3.org/TR/xmlschema-2/#base64Binary +func matchesXsBase64Binary(text string) bool { + if len(text)%4 != 0 { + return false + } + + if len(text) == 0 { + return true + } + + pads := 0 + if text[len(text)-1] == '=' { + pads = 1 + if text[len(text)-2] == '=' { + pads = 2 + } + } + + for i := 0; i < len(text)-pads; i++ { + character := text[i] + inAlphabet := (character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z') || + (character >= '0' && character <= '9') || + character == '+' || + character == '/' + if !inAlphabet { + return false + } + } + + // NOTE: + // Only these sixteen characters leave the two dropped bits at zero, and + // only these four leave the four dropped bits at zero. + if pads == 1 { + return strings.IndexByte("AEIMQUYcgkosw048", text[len(text)-2]) >= 0 + } + + if pads == 2 { + return strings.IndexByte("AQgw", text[len(text)-3]) >= 0 + } + + return true +} + +// Drop every whitespace character of `text`. +// +// This is what `xs:base64Binary` needs: it allows whitespace between +// the characters and not only around them, so collapsing is not enough -- +// the decoder accepts none of it. +func RemoveWhitespace(text string) string { + return whitespaceRunRe.ReplaceAllString(text, "") +} + +// The resulting `next` token points to the first token which is neither text +// nor comment. +// +// If we reached the end-of-file, `next` is an [Eof] sentinel token. +func ReadText( + decoder *xml.Decoder, + current xml.Token, +) (text string, next xml.Token, err error) { + b := &strings.Builder{} + + stop := false + for { + if _, isEOF := current.(Eof); isEOF { + err = NewDeserializationError( + "Expected to read text, but reached the end-of-file", + ) + return + } + + switch et := current.(type) { + case xml.CharData: + b.WriteString(string(et)) + // Proceed to the next token. + case xml.Comment: + // Proceed to the next token. + default: + stop = true + } + + if !stop { + current, err = ReadNext(decoder, current) + if err != nil { + return + } + } else { + break + } + } + + next = current + text = b.String() + return +} + +// Consume the text tokens (char data) as a representation of a `xs:boolean`. +// +// Any comment tokens are skipped. +// +// The resulting `next` token points to the first token which is neither text +// nor comment. +// +// If we reached the end-of-file, `next` is an [Eof] sentinel token. +func ReadTextAs_bool( + decoder *xml.Decoder, + current xml.Token, +) (value bool, next xml.Token, err error) { + var text string + text, next, err = ReadText(decoder, current) + if err != nil { + return + } + text = CollapseWhitespace(text) + + switch text { + case "1": + value = true + case "true": + value = true + case "0": + value = false + case "false": + value = false + default: + err = NewDeserializationError( + fmt.Sprintf( + "Expected a value as xs:boolean, but got: %s", + text, + ), + ) + } + if err != nil { + return + } + + return +} + +// Consume the text tokens (char data) as a representation of a `xs:long`. +// +// Any comment tokens are skipped. +// +// The resulting `next` token points to the first token which is neither text +// nor comment. +// +// If we reached the end-of-file, `next` is an [Eof] sentinel token. +func ReadTextAs_long( + decoder *xml.Decoder, + current xml.Token, +) (value int64, next xml.Token, err error) { + var text string + text, next, err = ReadText(decoder, current) + if err != nil { + return + } + text = CollapseWhitespace(text) + + var parseErr error + value, parseErr = strconv.ParseInt(text, 10, 64) + if parseErr != nil { + err = NewDeserializationError( + fmt.Sprintf( + "Expected a value as xs:long, but it could not be parsed: %s: %s", + parseErr.Error(), text, + ), + ) + return + } + + return +} + +func constructXsDoubleRe() *regexp.Regexp { + // NOTE: + // "+INF" is matched although it is written as "INF": XSD 1.1 admits it, + // its production being (\+|-)?INF, and being liberal in what we accept + // costs nothing here. strconv.ParseFloat reads it without complaint. + doubleRep := "((\\+|-)?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([Ee](\\+|-)?[0-9]+)?|(\\+|-)?INF|NaN)" + pattern := ourcommon.Concat( + "^", + doubleRep, + "$", + ) + + return regexp.MustCompile( + pattern, + ) +} + +var xsDoubleRe = constructXsDoubleRe() + +// Check that text conforms to the pattern of an `xs:double`. +// +// See: https://www.w3.org/TR/xmlschema-2/#double +// +// - `text`: Text to be checked +// - Return True if the text conforms to the pattern +func isValidXsDouble(text string) bool { + return xsDoubleRe.MatchString( + text, + ) +} + +// Consume the text tokens (char data) as a representation of a `xs:double`. +// +// Any comment tokens are skipped. +// +// The resulting `next` token points to the first token which is neither text +// nor comment. +// +// If we reached the end-of-file, `next` is an [Eof] sentinel token. +func ReadTextAs_double( + decoder *xml.Decoder, + current xml.Token, +) (value float64, next xml.Token, err error) { + var text string + text, next, err = ReadText(decoder, current) + if err != nil { + return + } + text = CollapseWhitespace(text) + + // We need to check explicitly for the regular expression since + // strconv.ParseFloat is too permissive. For example, it accepts "nan" + // although only "NaN" is valid. + // See: https://www.w3.org/TR/xmlschema-2/#double + if !isValidXsDouble(text) { + err = NewDeserializationError( + fmt.Sprintf( + "Expected a value as xs:double, but got: %s", + text, + ), + ) + return + } + + var parseErr error + value, parseErr = strconv.ParseFloat(text, 64) + // NOTE: + // A literal too large for a double is not an error in XSD. It rounds to + // an infinity, which is in the value space of xs:double, and ParseFloat + // hands us exactly that infinity *together* with [strconv.ErrRange]. So + // the range is deliberately let through, and only a syntax error is + // reported -- and the pattern above has already excluded those. + // + // A literal too small rounds to zero, which ParseFloat reports without + // any error at all. + // + // See: https://www.w3.org/TR/xmlschema11-2/#double + if parseErr != nil && !errors.Is(parseErr, strconv.ErrRange) { + err = NewDeserializationError( + fmt.Sprintf( + "Expected a value as xs:double, but it could not be parsed: %s: %s", + parseErr.Error(), text, + ), + ) + return + } + + // NOTE: + // We explicitly do not check for loss of precision, as the majority of people will + // use string representation of the floating point numbers ignoring the precision + // issues. For example, the closest double-precision number to the number `359.9` is + // `359.8999999999999772626324556767940521240234375`, but most people will simply + // give `359.9` as the value. + + return +} + +// Consume the text tokens (char data) as a base64-encoded bytes. +// +// Any comment tokens are skipped. +// +// The resulting `next` token points to the first token which is neither text +// nor comment. +// +// If we reached the end-of-file, `next` is an [Eof] sentinel token. +func ReadTextAs_bytes( + decoder *xml.Decoder, + current xml.Token, +) (value []byte, next xml.Token, err error) { + var text string + text, next, err = ReadText(decoder, current) + if err != nil { + return + } + // NOTE: + // xs:base64Binary allows whitespace between the characters, and not only + // around them, while the decoder accepts none of it. So every whitespace + // character is dropped, and not merely collapsed. + // + // See: https://www.w3.org/TR/xmlschema-2/#base64Binary + text = RemoveWhitespace(text) + + if !matchesXsBase64Binary(text) { + err = NewDeserializationError( + fmt.Sprintf( + "Expected a text as base64-encoded bytes, but got: %s", + text, + ), + ) + return + } + + var decodingErr error + value, decodingErr = b64.StdEncoding.DecodeString(text) + if decodingErr != nil { + err = NewDeserializationError( + fmt.Sprintf( + "Text could not be decoded as base64: %s", + decodingErr.Error(), + ), + ) + return + } + + return +} + +// Check that the `current` token is a valid start element, *i.e.*, lives in +// the `ns` namespace and contains no attributes. +func checkStartElementInNamespace( + current xml.StartElement, + ns string, +) (err error) { + unexpectedAttr := 0 + for _, attr := range current.Attr { + if (attr.Name.Space == "" && attr.Name.Local == "xmlns") || + attr.Name.Space == "xmlns" { + continue + } + + unexpectedAttr++ + } + if unexpectedAttr != 0 { + err = NewDeserializationError( + fmt.Sprintf( + "Expected no attributes except 'xmlns' in the start element, "+ + "but got %d in the start element %s", + unexpectedAttr, current.Name.Local, + ), + ) + return + } + + if current.Name.Space != ns { + err = NewDeserializationError( + fmt.Sprintf( + "Expected only start elements in the namespace %s, "+ + "but got a start element %s in the namespace %s", + ns, current.Name.Local, current.Name.Space, + ), + ) + return + } + + return +} + +// Expect a valid start element (as defined in [CheckStartElement]) and extract its +// `local` name. +// +// This function is meant to be called whenever you know the runtime type of a token. +// If you do not know the runtime type, call [ParseAsStartElementAndExtractLocalName] +// so that you can succinctly check the runtime type as well. +func extractLocalNameFromStartElementInNamespace( + current xml.StartElement, + ns string, +) (local string, err error) { + err = checkStartElementInNamespace(current, ns) + if err != nil { + return + } + + local = current.Name.Local + return +} + +// Expect a valid start element (as defined in [CheckStartElement]) and extract its +// local name. +// +// Valid means that we check that the start element lives in the `ns` namespace and +// contains no attributes. +// +// If you know the runtime type of `current` token, call +// [ExtractLocalNameFromStartElement] instead to save a cast. +func parseAsStartElementAndExtractLocalNameInNamespace( + current xml.Token, + ns string, +) (local string, err error) { + if _, isEOF := current.(Eof); isEOF { + err = NewDeserializationError( + "Expected a start element, but reached the end-of-file", + ) + return + } + + et, ok := current.(xml.StartElement) + if !ok { + switch v := current.(type) { + case xml.EndElement: + err = NewDeserializationError( + fmt.Sprintf( + "Expected a start element, but got an end element %s in namespace %s", + v.Name.Local, v.Name.Space, + ), + ) + case xml.CharData: + err = NewDeserializationError( + fmt.Sprintf( + "Expected a start element, but got text %s", + string(v), + ), + ) + default: + err = NewDeserializationError( + fmt.Sprintf( + "Expected a start element, but got %T: %v", + current, current, + ), + ) + } + return + } + + local, err = extractLocalNameFromStartElementInNamespace(et, ns) + return +} + +// Check that the `current` token is an end element, living in the `ns` namespace, +// and having the `local` name. +func checkEndElementInNamespace(current xml.Token, local string, ns string) (err error) { + if _, isEOF := current.(Eof); isEOF { + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s, but reached the end-of-file", + local, + ), + ) + return + } + + et, ok := current.(xml.EndElement) + if !ok { + switch v := current.(type) { + case xml.StartElement: + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s, but got a start element %s in namespace %s", + local, v.Name.Local, v.Name.Space, + ), + ) + case xml.CharData: + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s, but got text %s", + local, string(v), + ), + ) + default: + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s, but got %T: %v", + local, current, current, + ), + ) + } + return + } + + if et.Name.Space != ns { + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s in the namespace %s, "+ + "but got an end element in the namespace %s", + local, ns, et.Name.Space, + ), + ) + return + } + + if et.Name.Local != local { + err = NewDeserializationError( + fmt.Sprintf( + "Expected an end element %s, but got an end element %s", + local, et.Name.Local, + ), + ) + return + } + + return +} + +// Read a value wrapped in a single XML element which lives in the `ns` namespace, +// dispatching on the local name of that element. +// +// The element is read in full: the resulting `next` token points to the first token +// just after the end element. +// +// This is the *only* place which frames an XML element around a value. Every +// container which reads a sequence of elements delegates the framing here, so +// that its items differ only in the given `readByLocal`, and never in +// the container which reads them. +// +// `T` is left unconstrained since this function never invokes any method on it. +// This lets it be reused for an instance, for a scalar and for a JSON-able value +// alike. +func readElementDispatchedInNamespace[T any]( + decoder *xml.Decoder, + current xml.Token, + ns string, + readByLocal func( + aDecoder *xml.Decoder, + aCurrent xml.Token, + aLocal string, + ) (value T, aNext xml.Token, anErr error), +) (value T, next xml.Token, err error) { + current, err = SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + + var local string + local, err = parseAsStartElementAndExtractLocalNameInNamespace(current, ns) + if err != nil { + return + } + + // Move the current to the content of the XML element + current, err = ReadNext(decoder, current) + if err != nil { + return + } + + value, current, err = readByLocal(decoder, current, local) + if err != nil { + return + } + + err = checkEndElementInNamespace(current, local, ns) + if err != nil { + return + } + + next, err = ReadNext(decoder, current) + return +} + +// Check that the `current` token is a valid start element, *i.e.*, lives in +// the [Namespace] namespace and contains no attributes. +func CheckStartElement(current xml.StartElement) (err error) { + return checkStartElementInNamespace(current, Namespace) +} + +// Extract the local name of the given valid start element, which is expected to +// live in the [Namespace] namespace. +func ExtractLocalNameFromStartElement( + current xml.StartElement, +) (local string, err error) { + return extractLocalNameFromStartElementInNamespace(current, Namespace) +} + +// Expect a valid start element living in the [Namespace] namespace and extract +// its local name. +func ParseAsStartElementAndExtractLocalName( + current xml.Token, +) (local string, err error) { + return parseAsStartElementAndExtractLocalNameInNamespace(current, Namespace) +} + +// Check that the `current` token is an end element, living in the [Namespace] +// namespace, and having the `local` name. +func CheckEndElement(current xml.Token, local string) (err error) { + return checkEndElementInNamespace(current, local, Namespace) +} + +// Read a value wrapped in a single XML element living in the [Namespace] +// namespace, dispatching on the local name of that element. +func ReadElementDispatched[T any]( + decoder *xml.Decoder, + current xml.Token, + readByLocal func( + aDecoder *xml.Decoder, + aCurrent xml.Token, + aLocal string, + ) (value T, aNext xml.Token, anErr error), +) (value T, next xml.Token, err error) { + return readElementDispatchedInNamespace( + decoder, current, Namespace, readByLocal, + ) +} + +// Write the start element with the given `local` name to the encoder. +// +// Do not flush. +// +// If `withNamespace` is set, the element declares [Namespace]. The enclosing +// element is expected to have declared it otherwise. +func WriteStartElement( + encoder *xml.Encoder, + local string, + withNamespace bool, +) (err error) { + startElement := xml.StartElement{Name: xml.Name{Local: local}} + if withNamespace { + startElement.Name.Space = Namespace + } + + err = encoder.EncodeToken(startElement) + return +} + +// Write the end element with the given `local` name to the encoder. +// +// Do not flush. +// +// If `withNamespace` is set, the element is spelled in [Namespace], which +// the corresponding start element has to have declared. +func WriteEndElement( + encoder *xml.Encoder, + local string, + withNamespace bool, +) (err error) { + endElement := xml.EndElement{Name: xml.Name{Local: local}} + if withNamespace { + endElement.Name.Space = Namespace + } + + err = encoder.EncodeToken(endElement) + return +} + +// Write the `text` to the encoder. +// +// Do not flush. +// +// If `text` is empty, do nothing. +func WriteText( + encoder *xml.Encoder, + text string, +) (err error) { + if len(text) > 0 { + err = encoder.EncodeToken( + xml.CharData([]byte(text)), + ) + } + return +} + +// Write the `value` as a `xs:boolean` in a text element. +// +// Do not flush. +func WriteAsText_bool( + encoder *xml.Encoder, + value bool, +) (err error) { + text := "true" + if !value { + text = "false" + } + err = WriteText(encoder, text) + return +} + +// Write the `value` as a `xs:long` in a text element. +// +// Do not flush. +func WriteAsText_long( + encoder *xml.Encoder, + value int64, +) (err error) { + text := strconv.FormatInt(value, 10) + err = WriteText(encoder, text) + return +} + +// Write the `value` as a `xs:double` in a text element. +// +// Do not flush. +func WriteAsText_double( + encoder *xml.Encoder, + value float64, +) (err error) { + var text string + + // See: https://www.w3.org/TR/xmlschema-2/#double + // for the exact literals. + if math.IsInf(value, 0) { + if value < 0 { + text = "-INF" + } else { + text = "INF" + } + } else if math.IsNaN(value) { + text = "NaN" + } else { + text = strconv.FormatFloat(value, 'g', -1, 64) + } + + err = WriteText(encoder, text) + return +} + +// Write the `value` as a `xs:string` in a text element. +// +// Do not flush. +func WriteAsText_string( + encoder *xml.Encoder, + value string, +) (err error) { + err = WriteText(encoder, value) + return +} + +// Write the `value` as a base64-encoded bytes in a text element. +// +// Do not flush. +func WriteAsText_bytes( + encoder *xml.Encoder, + value []byte, +) (err error) { + text := b64.StdEncoding.EncodeToString( + value, + ) + + err = WriteText(encoder, text) + return +} + +// Write `that` as an XML element with the `local` name, its content written by +// `writeContent`. +// +// Do not flush. +// +// This is the one place which frames an XML element around a *value*: a property, +// a list item, a tuple item and an item of a JSON-able array all go through it, +// and differ only in the given `writeContent`. +// +// The XML namespace is expected to have been defined outside of the resulting XML +// element. +func WriteElement[T any]( + encoder *xml.Encoder, + local string, + that T, + writeContent func(anEncoder *xml.Encoder, aValue T) (anErr error), +) (err error) { + err = WriteStartElement(encoder, local, false) + if err != nil { + return + } + + err = writeContent(encoder, that) + if err != nil { + return + } + + err = WriteEndElement(encoder, local, false) + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/jsonization.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/jsonization.go new file mode 100644 index 000000000..5e5677859 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/jsonization.go @@ -0,0 +1,1156 @@ +// Package jsonization de/serializes model instances to and from JSON. +// +// We can not use one-pass deserialization for JSON since the object +// properties do not have fixed order, and hence we can not read +// `modelType` property ahead of the remaining properties. +// +// To de-serialize, call one of the `*FromJsonable` functions. +// +// To serialize, call [ToJsonable] function. +package jsonization + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "math" + b64 "encoding/base64" + ourreporting "github.com/dummy-works/dummy/reporting" + ourtypes "github.com/dummy-works/dummy/types" +) + +// region De-serialization + +// Represent an error during the de-serialization. +// +// Implements `error`. +type DeserializationError struct{ + Path *ourreporting.Path + Message string +} + +func newDeserializationError(message string) *DeserializationError { + return &DeserializationError{ + Path: &ourreporting.Path{}, + Message: message, + } +} + +func (de *DeserializationError) Error() string { + return fmt.Sprintf( + "%s: %s", + de.PathString(), + de.Message, + ) +} + +// Render the path as a string. +func (de *DeserializationError) PathString() string { + return ourreporting.ToJSONPath(de.Path) +} + +// Prepend the `name` segment to the path, and return the error back +// for chaining. +func (de *DeserializationError) prependName( + name string, +) *DeserializationError { + de.Path.PrependName( + &ourreporting.NameSegment{Name: name}, + ) + return de +} + +// Prepend the `index` segment to the path, and return the error back +// for chaining. +func (de *DeserializationError) prependIndex( + index int, +) *DeserializationError { + de.Path.PrependIndex( + &ourreporting.IndexSegment{Index: index}, + ) + return de +} + +// Prepend the `key` segment to the path, and return the error back +// for chaining. +func (de *DeserializationError) prependKey( + key string, +) *DeserializationError { + de.Path.PrependKey( + &ourreporting.KeySegment{Key: key}, + ) + return de +} + +// Cast `err` to a de-serialization error, or panic. +// +// Every error which originates in this package is +// a [DeserializationError], so the cast can only fail if a de-serialization +// snippet specific to an implementation returned a foreign error. +func mustDeserializationError(err error) *DeserializationError { + deseriaErr, ok := err.(*DeserializationError) + if !ok { + panic( + fmt.Sprintf( + "Expected a *DeserializationError, but got %T: %v", + err, + err, + ), + ) + } + return deseriaErr +} + +// Parse `jsonable` as a boolean, or return an error. +func boolFromJsonable( + jsonable interface{}, +) (result bool, err error) { + if jsonable == nil { + err = newDeserializationError( + "Expected a boolean, but got null", + ) + return + } + + var ok bool + result, ok = jsonable.(bool) + if !ok { + err = newDeserializationError( + fmt.Sprintf("Expected a boolean, but got %T", jsonable), + ) + + return + } + + return +} + +// Parse `jsonable` as a 64-bit integer, or return an error. +func int64FromJsonable( + jsonable interface{}, +) (result int64, err error) { + if jsonable == nil { + err = newDeserializationError( + "Expected an integer number, but got null", + ) + return + } + + f, ok := jsonable.(float64) + if !ok { + err = newDeserializationError( + fmt.Sprintf( + "Expected an integer number, but got %T", + jsonable, + ), + ) + return + } + + if math.IsNaN(f) { + err = newDeserializationError( + "Expected an integer number, but got a NaN", + ) + return + } + + if math.IsInf(f, 0) { + err = newDeserializationError( + "Expected an integer number, but got an infinity", + ) + return + } + + if f != math.Trunc(f) { + err = newDeserializationError( + fmt.Sprintf( + "Expected an integer number, but got a non-integer: %v", + f, + ), + ) + return + } + + result = int64(f) + if f != float64(result) { + err = newDeserializationError( + fmt.Sprintf( + "Expected an integer number fitting into int64, but got: %v", + jsonable, + ), + ) + return + } + + return +} + +// Parse `jsonable` as a 64-bit float, or return an error. +func float64FromJsonable( + jsonable interface{}, +) (result float64, err error) { + if jsonable == nil { + err = newDeserializationError( + "Expected a number, but got null", + ) + return + } + + var ok bool + result, ok = jsonable.(float64) + if !ok { + err = newDeserializationError( + fmt.Sprintf( + "Expected a number, but got %T", + jsonable, + ), + ) + return + } + + // NOTE (mristin): + // JSON knows neither an infinity nor a not-a-number, so a conformant parser + // can never give us one. The caller can still hand us a JSON-able which has + // been constructed programmatically, so we have to check here. + + if math.IsNaN(result) || math.IsInf(result, 0) { + err = newDeserializationError( + fmt.Sprintf( + "Expected a finite number, but got: %v", + result, + ), + ) + result = 0 + return + } + + return +} + +// Parse `jsonable` as a string, or return an error. +func stringFromJsonable( + jsonable interface{}, +) (result string, err error) { + if jsonable == nil { + err = newDeserializationError( + "Expected a string, but got null", + ) + return + } + + var ok bool + result, ok = jsonable.(string) + if ok { + return + } else { + err = newDeserializationError( + fmt.Sprintf("Expected a string, but got %T", jsonable), + ) + return + } +} + +// Parse `jsonable` as a byte array, or return an error. +func bytesFromJsonable( + jsonable interface{}, +) (result []byte, err error) { + if jsonable == nil { + err = newDeserializationError( + "Expected a base64-encoded string, but got null", + ) + return + } + + text, ok := jsonable.(string) + if !ok { + err = newDeserializationError( + fmt.Sprintf( + "Expected a base64-encoded string, but got %T", + jsonable, + ), + ) + return + } + + var decodingErr error + result, decodingErr = b64.StdEncoding.DecodeString(text) + if decodingErr != nil { + err = newDeserializationError( + fmt.Sprintf( + "String could not be decoded as base64: %s", + decodingErr.Error(), + ), + ) + return + } + + return +} + +// Return a pointer to the `value`, or the `err`, if any. +// +// This function takes the *results* of a parse function instead of the parse +// function itself. Go binds all the results of a call to the whole parameter +// list of the enclosing call, so this single helper composes with every parse +// function, however many arguments that function takes -- including +// `parseOptional(parseTuple2(v, ...))`, which a parser-taking signature could +// not express, as the item parsers of a tuple vary both in number and in type. +func parseOptional[T any](value T, err error) (*T, error) { + if err != nil { + return nil, err + } + return &value, nil +} + +// Report that `jsonable` is no JSON object. +func notAMapError(jsonable interface{}) error { + if jsonable == nil { + return newDeserializationError( + "Expected a JSON object, but got null", + ) + } + + return newDeserializationError( + fmt.Sprintf( + "Expected a JSON object, but got %T", + jsonable, + ), + ) +} + +// Extract the `modelType` property of `m` as a string, or return an error. +// +// This is the only place which knows how the model type is spelled on the wire. +// Both the dispatch on the model type and its check in a concrete class go +// through it. +func modelTypeFromMap( + m map[string]interface{}, +) (modelType string, err error) { + jsonable, ok := m["modelType"] + if !ok { + err = newDeserializationError( + "The required property modelType is missing", + ) + return + } + + modelType, err = stringFromJsonable(jsonable) + if err != nil { + mustDeserializationError(err).prependName("modelType") + } + return +} + +// Check that `m` specifies the `expected` model type, or return an error. +func checkModelType( + m map[string]interface{}, + expected string, +) (err error) { + var modelType string + modelType, err = modelTypeFromMap(m) + if err != nil { + return + } + + if modelType != expected { + // NOTE (mristin): + // A wrong model type is reported at the instance, as an unknown one is, + // and as in the other SDKs. + err = newDeserializationError( + fmt.Sprintf( + "Expected the model type '%s', but got %s", + expected, + modelType, + ), + ) + } + return +} + +// Parse `jsonable` as an array and parse every item with `parseItem`, +// or return an error. +func parseArray[T any]( + jsonable interface{}, + parseItem func(jsonable interface{}) (T, error), +) (result []T, err error) { + jsonableArray, ok := jsonable.([]interface{}) + if !ok { + err = newDeserializationError( + fmt.Sprintf( + "Expected an array, but got %T", + jsonable, + ), + ) + return + } + + result = make([]T, len(jsonableArray)) + for i, itemJsonable := range jsonableArray { + var item T + item, err = parseItem(itemJsonable) + if err != nil { + mustDeserializationError(err).prependIndex(i) + return + } + result[i] = item + } + return +} + +// Parse `jsonable` as an instance of [ourtypes.IItem], +// or return an error. +func ItemFromJsonable( + jsonable interface{}, +) ( + result ourtypes.IItem, + err error, +) { + m, ok := jsonable.(map[string]interface{}) + if !ok { + err = notAMapError(jsonable) + return + } + + return itemFromMap(m) +} + +// Parse `jsonable` as an instance of [ourtypes.ISomeItem], +// or return an error. +func SomeItemFromJsonable( + jsonable interface{}, +) ( + result ourtypes.ISomeItem, + err error, +) { + m, ok := jsonable.(map[string]interface{}) + if !ok { + err = notAMapError(jsonable) + return + } + + return someItemFromMap(m) +} + +// Parse [ourtypes.ISomeItem] from a map, +// or return an error, if any. +// +// This function performs no dispatch! It is used to parse the properties +// as-are, and already assumes the exact model type. Usually, this function +// is called from within a from-jsonable or from-map function, and you never +// call it directly. If you want to de-serialize an instance of +// [ourtypes.ISomeItem], call +// [SomeItemFromJsonable]. +func someItemFromMapWithoutDispatch( + m map[string]interface{}, +) ( + result ourtypes.ISomeItem, + err error, +) { + var theName string + var theNickname *string + + foundName := false + + for k, v := range m { + switch k { + case "name": + theName, err = stringFromJsonable(v) + foundName = true + + case "nickname": + theNickname, err = parseOptional(stringFromJsonable(v)) + + case "modelType": + // The model type has already been checked before the loop. + + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected property: %s", + k, + ), + ) + return + } + + if err != nil { + mustDeserializationError(err).prependName(k) + return + } + } + + if !foundName { + err = newDeserializationError( + "The required property 'name' is missing", + ) + return + } + + result = ourtypes.NewSomeItem( + theName, + ) + result.SetNickname( + theNickname, + ) + + return +} + +// Parse `jsonable` as an instance of [ourtypes.ISpecialItem], +// or return an error. +func SpecialItemFromJsonable( + jsonable interface{}, +) ( + result ourtypes.ISpecialItem, + err error, +) { + m, ok := jsonable.(map[string]interface{}) + if !ok { + err = notAMapError(jsonable) + return + } + + err = checkModelType(m, "SpecialItem") + if err != nil { + return + } + + return specialItemFromMapWithoutDispatch(m) +} + +// Parse [ourtypes.ISpecialItem] from a map, +// or return an error, if any. +func specialItemFromMapWithoutDispatch( + m map[string]interface{}, +) ( + result ourtypes.ISpecialItem, + err error, +) { + var theName string + var theNickname *string + var theLevel int64 + + foundName := false + foundLevel := false + + for k, v := range m { + switch k { + case "name": + theName, err = stringFromJsonable(v) + foundName = true + + case "nickname": + theNickname, err = parseOptional(stringFromJsonable(v)) + + case "level": + theLevel, err = int64FromJsonable(v) + foundLevel = true + + case "modelType": + // The model type has already been checked before the loop. + + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected property: %s", + k, + ), + ) + return + } + + if err != nil { + mustDeserializationError(err).prependName(k) + return + } + } + + if !foundName { + err = newDeserializationError( + "The required property 'name' is missing", + ) + return + } + + if !foundLevel { + err = newDeserializationError( + "The required property 'level' is missing", + ) + return + } + + result = ourtypes.NewSpecialItem( + theName, + theLevel, + ) + result.SetNickname( + theNickname, + ) + + return +} + +// Parse `jsonable` as an instance of [ourtypes.IContainer], +// or return an error. +func ContainerFromJsonable( + jsonable interface{}, +) ( + result ourtypes.IContainer, + err error, +) { + m, ok := jsonable.(map[string]interface{}) + if !ok { + err = notAMapError(jsonable) + return + } + + return containerFromMapWithoutDispatch(m) +} + +// Parse [ourtypes.IContainer] from a map, +// or return an error, if any. +func containerFromMapWithoutDispatch( + m map[string]interface{}, +) ( + result ourtypes.IContainer, + err error, +) { + var theItems []ourtypes.IItem + var theSomeItems []ourtypes.ISomeItem + var theSpecialItems []ourtypes.ISpecialItem + + foundItems := false + foundSomeItems := false + + for k, v := range m { + switch k { + case "items": + theItems, err = parseArray(v, ItemFromJsonable) + foundItems = true + + case "someItems": + theSomeItems, err = parseArray(v, SomeItemFromJsonable) + foundSomeItems = true + + case "specialItems": + theSpecialItems, err = parseArray(v, SpecialItemFromJsonable) + + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected property: %s", + k, + ), + ) + return + } + + if err != nil { + mustDeserializationError(err).prependName(k) + return + } + } + + if !foundItems { + err = newDeserializationError( + "The required property 'items' is missing", + ) + return + } + + if !foundSomeItems { + err = newDeserializationError( + "The required property 'someItems' is missing", + ) + return + } + + result = ourtypes.NewContainer( + theItems, + theSomeItems, + ) + result.SetSpecialItems( + theSpecialItems, + ) + + return +} + +// Parse `jsonable` as an instance of [ourtypes.IWrapper], +// or return an error. +func WrapperFromJsonable( + jsonable interface{}, +) ( + result ourtypes.IWrapper, + err error, +) { + m, ok := jsonable.(map[string]interface{}) + if !ok { + err = notAMapError(jsonable) + return + } + + return wrapperFromMapWithoutDispatch(m) +} + +// Parse [ourtypes.IWrapper] from a map, +// or return an error, if any. +func wrapperFromMapWithoutDispatch( + m map[string]interface{}, +) ( + result ourtypes.IWrapper, + err error, +) { + var theContainer ourtypes.IContainer + + foundContainer := false + + for k, v := range m { + switch k { + case "container": + theContainer, err = ContainerFromJsonable(v) + foundContainer = true + + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected property: %s", + k, + ), + ) + return + } + + if err != nil { + mustDeserializationError(err).prependName(k) + return + } + } + + if !foundContainer { + err = newDeserializationError( + "The required property 'container' is missing", + ) + return + } + + result = ourtypes.NewWrapper( + theContainer, + ) + + return +} + +// De-serialize an instance of [ourtypes.IItem] +// from a map by dispatching to the concrete `*FromMapWithoutDispatch` function. +func itemFromMap( + m map[string]interface{}, +) ( + result ourtypes.IItem, + err error, +) { + var modelType string + modelType, err = modelTypeFromMap(m) + if err != nil { + return + } + + switch modelType { + case "SomeItem": + result, err = someItemFromMapWithoutDispatch(m) + case "SpecialItem": + result, err = specialItemFromMapWithoutDispatch(m) + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected model type " + + "for IItem: %s", + modelType, + ), + ) + } + + return +} + +// De-serialize an instance of [ourtypes.ISomeItem] +// from a map by dispatching to the concrete `*FromMapWithoutDispatch` function. +func someItemFromMap( + m map[string]interface{}, +) ( + result ourtypes.ISomeItem, + err error, +) { + var modelType string + modelType, err = modelTypeFromMap(m) + if err != nil { + return + } + + switch modelType { + case "SpecialItem": + result, err = specialItemFromMapWithoutDispatch(m) + case "SomeItem": + result, err = someItemFromMapWithoutDispatch(m) + default: + err = newDeserializationError( + fmt.Sprintf( + "Unexpected model type " + + "for ISomeItem: %s", + modelType, + ), + ) + } + + return +} + +// endregion + +// region Serialization + +// Represent an error during the serialization. +// +// Implements `error`. +type SerializationError struct{ + Path *ourreporting.Path + Message string +} + +func newSerializationError(message string) *SerializationError { + return &SerializationError{ + Path: &ourreporting.Path{}, + Message: message, + } +} + +func (se *SerializationError) Error() string { + return fmt.Sprintf( + "%s: %s", + se.PathString(), + se.Message, + ) +} + +// Render the path as a string. +func (se *SerializationError) PathString() string { + return ourreporting.ToGolangPath(se.Path) +} + +// Prepend the `name` segment to the path, and return the error back +// for chaining. +func (se *SerializationError) prependName( + name string, +) *SerializationError { + se.Path.PrependName( + &ourreporting.NameSegment{Name: name}, + ) + return se +} + +// Prepend the `index` segment to the path, and return the error back +// for chaining. +func (se *SerializationError) prependIndex( + index int, +) *SerializationError { + se.Path.PrependIndex( + &ourreporting.IndexSegment{Index: index}, + ) + return se +} + +// Prepend the `key` segment to the path, and return the error back +// for chaining. +func (se *SerializationError) prependKey( + key string, +) *SerializationError { + se.Path.PrependKey( + &ourreporting.KeySegment{Key: key}, + ) + return se +} + +// Cast `err` to a serialization error, or panic. +// +// Every error which originates in this package is a [SerializationError], +// so the cast can only fail if a serialization snippet specific to +// an implementation returned a foreign error. +func mustSerializationError(err error) *SerializationError { + seriaErr, ok := err.(*SerializationError) + if !ok { + panic( + fmt.Sprintf( + "Expected a *SerializationError, but got %T: %v", + err, + err, + ), + ) + } + return seriaErr +} + +// Try to cast `that` to a float64 and box it as a JSON-able value, or +// return an error. +// +// The result is returned as `interface{}`, not the more specific +// `float64`, so that this function itself can be passed on as a bare +// reference wherever a `func(int64) (interface{}, error)` is expected, +// e.g. as an item (de)serializer in a list or a tuple. +func int64ToJsonable( + that int64, +) (result interface{}, err error) { + if that > 9007199254740991 || that < -9007199254740991 { + err = newSerializationError( + fmt.Sprintf( + "64-bit integer can not be represented as 64-bit float in JSON: %v", + that, + ), + ) + return + } + + result = float64(that); + return +} + +// Box `that` as a JSON-able value, or return an error. +// +// JSON knows neither an infinity nor a not-a-number, so we refuse to +// serialize them instead of leaving it to `json.Marshal` to fail much later, +// with no path to the culprit. +// +// The result is returned as `interface{}`, not the more specific +// `float64`, so that this function itself can be passed on as a bare +// reference wherever a `func(float64) (interface{}, error)` is expected, +// e.g. as an item (de)serializer in a list or a tuple. +func float64ToJsonable( + that float64, +) (result interface{}, err error) { + if math.IsNaN(that) { + err = newSerializationError( + "A not-a-number can not be serialized to JSON", + ) + return + } + + if math.IsInf(that, 0) { + err = newSerializationError( + fmt.Sprintf( + "An infinity can not be serialized to JSON: %v", + that, + ), + ) + return + } + + result = that + return +} + +// Encode `bytes` to a base64 string and box it as a JSON-able value, or +// return an error. +// +// The result is returned as `interface{}`, not the more specific +// `string`, so that this function itself can be passed on as a bare +// reference wherever a `func([]byte) (interface{}, error)` is expected, +// e.g. as an item (de)serializer in a list or a tuple. +func bytesToJsonable( + bytes []byte, +) (result interface{}, err error) { + if bytes == nil { + err = newSerializationError( + "Expected an array of bytes, but got nil", + ) + return + } + + result = b64.StdEncoding.EncodeToString( + bytes, + ) + return +} + +// Serialize every item of `items` with `serializeItem` into a JSON-able array, +// or return an error. +func serializeArray[T any]( + items []T, + serializeItem func(item T) (interface{}, error), +) (result []interface{}, err error) { + result = make([]interface{}, len(items)) + for i, item := range items { + result[i], err = serializeItem(item) + if err != nil { + mustSerializationError(err).prependIndex(i) + return + } + } + return +} + +// Serialize `that` to a JSON-able value, or return an error. +// +// `ToJsonable` takes an `ourtypes.IClass`, but a list or a tuple item's own +// (more specific) interface type, e.g., `ourtypes.ISomeItem`, can not be +// unified with that when passing `ToJsonable` itself as a +// `func(item T) (interface{}, error)` value -- Go function values are +// invariant in their parameter type (no contravariance, unlike, say, a C# +// delegate). Making this wrapper itself generic (instead of fixing its +// parameter to `ourtypes.IClass`) lets the very same one be passed on bare, +// uninstantiated, for every class-typed item regardless of its +// concrete interface: Go infers both the item's type and this +// wrapper's own type parameter together from the context of the +// `serializeArray`/`serializeTupleN` call. +func classAsJsonableInterface[T ourtypes.IClass](that T) (interface{}, error) { + return ToJsonable(that) +} + +// Serialize [ourtypes.ISomeItem] as a JSON-able map. +// +// This function performs no dispatch! It is only used to serialize +// the properties. If you want to serialize an instance of +// [ourtypes.ISomeItem] with proper dispatch, call +// [ToJsonable]. +func someItemToMap( + that ourtypes.ISomeItem, +) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + + result["name"] = that.Name() + + if that.Nickname() != nil { + result["nickname"] = *that.Nickname() + } + + result["modelType"] = "SomeItem" + + return +} + +// Serialize [ourtypes.ISpecialItem] as a JSON-able map. +// +// This function performs no dispatch! It is only used to serialize +// the properties. If you want to serialize an instance of +// [ourtypes.ISpecialItem] with proper dispatch, call +// [ToJsonable]. +func specialItemToMap( + that ourtypes.ISpecialItem, +) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + + result["name"] = that.Name() + + if that.Nickname() != nil { + result["nickname"] = *that.Nickname() + } + + result["level"], err = int64ToJsonable(that.Level()) + if err != nil { + mustSerializationError(err).prependName("Level()") + return + } + + result["modelType"] = "SpecialItem" + + return +} + +// Serialize [ourtypes.IContainer] as a JSON-able map. +// +// This function performs no dispatch! It is only used to serialize +// the properties. If you want to serialize an instance of +// [ourtypes.IContainer] with proper dispatch, call +// [ToJsonable]. +func containerToMap( + that ourtypes.IContainer, +) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + + result["items"], err = serializeArray( + that.Items(), classAsJsonableInterface[ourtypes.IItem], + ) + if err != nil { + mustSerializationError(err).prependName("Items()") + return + } + + result["someItems"], err = serializeArray( + that.SomeItems(), classAsJsonableInterface[ourtypes.ISomeItem], + ) + if err != nil { + mustSerializationError(err).prependName("SomeItems()") + return + } + + if that.SpecialItems() != nil { + result["specialItems"], err = serializeArray( + that.SpecialItems(), classAsJsonableInterface[ourtypes.ISpecialItem], + ) + if err != nil { + mustSerializationError(err).prependName("SpecialItems()") + return + } + } + + return +} + +// Serialize [ourtypes.IWrapper] as a JSON-able map. +// +// This function performs no dispatch! It is only used to serialize +// the properties. If you want to serialize an instance of +// [ourtypes.IWrapper] with proper dispatch, call +// [ToJsonable]. +func wrapperToMap( + that ourtypes.IWrapper, +) (result map[string]interface{}, err error) { + result = make(map[string]interface{}) + + result["container"], err = ToJsonable(that.Container()) + if err != nil { + mustSerializationError(err).prependName("Container()") + return + } + + return +} + +// Serialize “that“ instance to a JSON-able representation. +// +// Return a structure which can be readily converted to JSON, +// or an error if some value could not be converted. +func ToJsonable( + that ourtypes.IClass, +) (result map[string]interface{}, err error) { + switch that.ModelType() { + case ourtypes.ModelTypeSomeItem: + result, err = someItemToMap( + that.(ourtypes.ISomeItem), + ) + case ourtypes.ModelTypeSpecialItem: + result, err = specialItemToMap( + that.(ourtypes.ISpecialItem), + ) + case ourtypes.ModelTypeContainer: + result, err = containerToMap( + that.(ourtypes.IContainer), + ) + case ourtypes.ModelTypeWrapper: + result, err = wrapperToMap( + that.(ourtypes.IWrapper), + ) + default: + err = newSerializationError( + fmt.Sprintf( + "Unexpected model type literal: %v", + that.ModelType(), + ), + ) + } + return +} + +// endregion + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/classes_with_descendants_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/classes_with_descendants_test.go new file mode 100644 index 000000000..162a7b981 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/classes_with_descendants_test.go @@ -0,0 +1,130 @@ +package jsonization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "testing" + ourjsonization "github.com/dummy-works/dummy/jsonization" + ourtesting "github.com/dummy-works/dummy/ourtesting" +) + +func TestItemRoundTripOKOverDescendant(t *testing.T) { + instance := ourtesting.MustLoadMinimalSomeItem() + + jsonable, err := ourjsonization.ToJsonable(instance) + if err != nil { + t.Fatalf( + "Failed to serialize the minimal SomeItem: %v", + err, + ) + return + } + + source := "" + + deserialized, deseriaErr := ourjsonization.ItemFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, source) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, source) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + source, + ) + if !ok { + return + } +} + +func TestItemDeserializationFail(t *testing.T) { + jsonable := any("this is not an object") + + _, err := ourjsonization.ItemFromJsonable( + jsonable, + ) + + if err == nil { + t.Fatal("Expected an error, but got none.") + return + } + + deseriaErr, ok := err.(*ourjsonization.DeserializationError) + if !ok { + t.Fatalf("Expected a de-serialization error, but got: %v", err) + return + } + + pathString := deseriaErr.PathString() + if len(pathString) != 0 { + t.Fatalf( + "Expected an empty path in error, but got: %s", + pathString, + ) + return + } + + expectedMessage := + "Expected a JSON object, but got string" + + if deseriaErr.Message != expectedMessage { + t.Fatalf( + "Expected the deserialization error:\n%s\n, but got:\n%s", + expectedMessage, deseriaErr.Message, + ) + return + } +} + +func TestSomeItemRoundTripOKOverDescendant(t *testing.T) { + instance := ourtesting.MustLoadMinimalSpecialItem() + + jsonable, err := ourjsonization.ToJsonable(instance) + if err != nil { + t.Fatalf( + "Failed to serialize the minimal SpecialItem: %v", + err, + ) + return + } + + source := "" + + deserialized, deseriaErr := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, source) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, source) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + source, + ) + if !ok { + return + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/common_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/common_test.go new file mode 100644 index 000000000..98f55b485 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/common_test.go @@ -0,0 +1,189 @@ +package jsonization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + ourjsonization "github.com/dummy-works/dummy/jsonization" + ourtesting "github.com/dummy-works/dummy/ourtesting" +) + +// Assert that there is no de-serialization error when de-serializing +// from `source`. +func assertNoDeserializationError( + t *testing.T, + err error, + source string, +) (ok bool) { + ok = true + if err != nil { + ok = false + t.Fatalf( + "Expected no de-serialization error from %s, "+ + "but got: %v", + source, err, + ) + return + } + return +} + +// Assert that there is no serialization error when serializing the instance +// originally coming from `source`. +func assertNoSerializationError( + t *testing.T, + err error, + source string, +) (ok bool) { + ok = true + if err != nil { + ok = false + t.Fatalf( + "Expected no serialization error when serializing "+ + "the instance obtained from %s, "+ + "but got: %v", + source, err, + ) + return + } + return +} + +// Assert that the serialization `other`, as JSON-able, equals the original +// JSON-able `that` read from `source`. +func assertSerializationEqualsDeserialization( + t *testing.T, + that interface{}, + other interface{}, + source string, +) (ok bool) { + ok = true + + thatBytes, err := json.Marshal(that) + if err != nil { + panic(fmt.Sprintf("Failed to marshal that jsonable %v: %s", that, err.Error())) + } + thatText := string(thatBytes) + + otherBytes, err := json.Marshal(other) + if err != nil { + panic( + fmt.Sprintf( + "Failed to marshal other jsonable %v: %s", other, err.Error(), + ), + ) + } + otherText := string(otherBytes) + + if thatText != otherText { + ok = false + t.Fatalf( + "The serialization of the de-serialized instance from %s does not equal "+ + "the original JSON-able:\nOriginal:\n%s\nSerialized:\n%s", + source, thatText, otherText, + ) + return + } + + return +} + +// Assert that there is a de-serialization error. +// +// If [ourtesting.RecordMode] is set, the de-serialization error is re-recorded +// to `expectedPth`. Otherwise, the error is compared against the golden file +// `expectedPth`. +func assertDeserializationErrorEqualsExpectedOrRecord( + t *testing.T, + err error, + source string, + expectedPth string, +) (ok bool) { + ok = true + + if err == nil { + ok = false + t.Fatalf("De-serialization error expected from %s, but got none", source) + return + } + + var deseriaErr *ourjsonization.DeserializationError + deseriaErr, ok = err.(*ourjsonization.DeserializationError) + if !ok { + t.Fatalf("Expected a de-serialization error, but got: %v from %s", err, source) + return + } + + // Add a new line for POSIX systems. + got := deseriaErr.Error() + "\n" + + if ourtesting.RecordMode { + parent := filepath.Dir(expectedPth) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + panic( + fmt.Sprintf( + "Failed to create the directory %s: %s", parent, err.Error(), + ), + ) + } + + err = os.WriteFile(expectedPth, []byte(got), 0644) + if err != nil { + panic( + fmt.Sprintf( + "Failed to write to the file %s: %s", expectedPth, err.Error(), + ), + ) + } + } else { + _, err := os.Stat(expectedPth) + if err != nil { + ok = false + t.Fatalf( + "Failed to stat the file %s: %s; if the file does not exist, "+ + "you probably want to record the test data by "+ + "setting the environment variable %s", + expectedPth, err.Error(), ourtesting.RecordModeEnvironmentVariableName, + ) + return + } + + bb, err := os.ReadFile(expectedPth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to read from file %s: %s", expectedPth, err.Error(), + ), + ) + } + + expected := string(bb) + + // NOTE (mristin): + // Git automatically strips and adds `\r`, so we have to remove it here + // to obtain a canonical text. + expected = strings.Replace(expected, "\r", "", -1) + + if expected != got { + ok = false + t.Fatalf( + "What we got differs from the expected in %s. "+ + "We got:\n%s\nWe expected:\n%s", + expectedPth, got, expected, + ) + return + } + } + + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/concrete_classes_test.go new file mode 100644 index 000000000..e1f2ee7ea --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/concrete_classes_test.go @@ -0,0 +1,521 @@ +package jsonization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "path/filepath" + "sort" + "testing" + ourjsonization "github.com/dummy-works/dummy/jsonization" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Determine the path to the first recorded example of `modelType`. +func mustFirstExpectedPath(t *testing.T, modelType string) string { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + modelType, + ), + ".json", + ) + sort.Strings(pths) + + if len(pths) == 0 { + t.Fatalf( + "Expected at least one recorded example of %s, but got none", + modelType, + ) + } + + return pths[0] +} + +// Assert that `that` can not be serialized to JSON, and that the failure +// is reported at `expectedPath`. +func assertSerializationFailsAt( + t *testing.T, + that ourtypes.IClass, + expectedPath string, +) { + _, err := ourjsonization.ToJsonable(that) + + if err == nil { + t.Fatalf( + "Expected the serialization to fail at %s, but it succeeded", + expectedPath, + ) + return + } + + seriaErr, ok := err.(*ourjsonization.SerializationError) + if !ok { + t.Fatalf( + "Expected a *SerializationError, but got %T: %v", + err, err, + ) + return + } + + if seriaErr.PathString() != expectedPath { + t.Fatalf( + "Expected the serialization to fail at %s, "+ + "but it failed at %s: %s", + expectedPath, seriaErr.PathString(), seriaErr.Message, + ) + } +} + +func TestSomeItemRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "SomeItem", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + pth, + ) + if !ok { + return + } + } +} + +func TestSomeItemDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "SomeItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + _, deseriaErr := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + ok := assertDeserializationErrorEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestSpecialItemRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "SpecialItem", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + pth, + ) + if !ok { + return + } + } +} + +func TestSpecialItemDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "SpecialItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + _, deseriaErr := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + ok := assertDeserializationErrorEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestContainerRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "Container", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.ContainerFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + pth, + ) + if !ok { + return + } + } +} + +func TestContainerDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "Container", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + _, deseriaErr := ourjsonization.ContainerFromJsonable( + jsonable, + ) + ok := assertDeserializationErrorEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestWrapperRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "Wrapper", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.WrapperFromJsonable( + jsonable, + ) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + anotherJsonable, seriaErr := ourjsonization.ToJsonable(deserialized) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + ok = assertSerializationEqualsDeserialization( + t, + jsonable, + anotherJsonable, + pth, + ) + if !ok { + return + } + } +} + +func TestWrapperDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "Wrapper", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + _, deseriaErr := ourjsonization.WrapperFromJsonable( + jsonable, + ) + ok := assertDeserializationErrorEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestSpecialItemSerializationFailOnOutOfRangeLevel(t *testing.T) { + pth := mustFirstExpectedPath(t, "SpecialItem") + + for _, value := range []int64{ + 9007199254740992, + -9007199254740992, + } { + jsonable := ourtesting.MustReadJsonable(pth) + + instance, deseriaErr := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + if !assertNoDeserializationError(t, deseriaErr, pth) { + return + } + + instance.SetLevel(value) + + assertSerializationFailsAt( + t, + instance, + "Level()", + ) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/enums_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/enums_test.go new file mode 100644 index 000000000..1fd5a3ce4 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/jsonization/test/enums_test.go @@ -0,0 +1,11 @@ +package jsonization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + +) + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/common_jsonization.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/common_jsonization.go new file mode 100644 index 000000000..ee8cf4a15 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/common_jsonization.go @@ -0,0 +1,350 @@ +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "path" + ourjsonization "github.com/dummy-works/dummy/jsonization" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Load a maximal example of [ourtypes.ISomeItem] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMaximalSomeItem( +) (result ourtypes.ISomeItem) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "SomeItem", + "maximal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of ISomeItem " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.ISomeItem) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of ISomeItem at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a minimal example of [ourtypes.ISomeItem] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMinimalSomeItem( +) (result ourtypes.ISomeItem) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "SomeItem", + "minimal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of ISomeItem " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.ISomeItem) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of ISomeItem at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a maximal example of [ourtypes.ISpecialItem] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMaximalSpecialItem( +) (result ourtypes.ISpecialItem) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "SpecialItem", + "maximal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of ISpecialItem " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.ISpecialItem) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of ISpecialItem at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a minimal example of [ourtypes.ISpecialItem] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMinimalSpecialItem( +) (result ourtypes.ISpecialItem) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "SpecialItem", + "minimal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of ISpecialItem " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.ISpecialItem) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of ISpecialItem at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a maximal example of [ourtypes.IContainer] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMaximalContainer( +) (result ourtypes.IContainer) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "Container", + "maximal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.ContainerFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of IContainer " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.IContainer) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of IContainer at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a minimal example of [ourtypes.IContainer] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMinimalContainer( +) (result ourtypes.IContainer) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "Container", + "minimal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.ContainerFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of IContainer " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.IContainer) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of IContainer at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a maximal example of [ourtypes.IWrapper] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMaximalWrapper( +) (result ourtypes.IWrapper) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "Wrapper", + "maximal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.WrapperFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of IWrapper " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.IWrapper) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of IWrapper at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// Load a minimal example of [ourtypes.IWrapper] from +// the test data directory. +// +// If there is any error, panic. +func MustLoadMinimalWrapper( +) (result ourtypes.IWrapper) { + pth := path.Join( + TestDataDir, + "Json", + "Expected", + "Wrapper", + "minimal.json", + ) + + jsonable := MustReadJsonable(pth) + + instance, err := ourjsonization.WrapperFromJsonable( + jsonable, + ) + if err != nil { + panic( + fmt.Sprintf( + "Failed to de-serialize an instance of IWrapper " + + "from %s: %s", + pth, err.Error(), + ), + ) + } + var ok bool + result, ok = instance.(ourtypes.IWrapper) + if !ok { + panic( + fmt.Sprintf( + "Expected to find an instance of IWrapper at %s, " + + "but got an instance of %T: %v", + pth, instance, instance, + ), + ) + } + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/constants.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/constants.go new file mode 100644 index 000000000..38df0b3df --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/constants.go @@ -0,0 +1,43 @@ +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "os" + "strings" +) + +const RecordModeEnvironmentVariableName string = ( + "GITHUB_COM_DUMMY_WORKS_DUMMY_TEST_RECORD_MODE") + +// NOTE (mristin): +// It is tedious to record manually all the expected error messages. Therefore we +// include this variable to steer the automatic recording. We intentionally +// intertwine the recording code with the test code to keep them close to each other +// so that they are easier to maintain. +var rM = os.Getenv(RecordModeEnvironmentVariableName) +var RecordMode = rM == "1" || strings.ToLower(rM) == "true" || strings.ToLower(rM) == "on" + +const TestDataDirEnvironmentVariableName string = ( + "GITHUB_COM_DUMMY_WORKS_DUMMY_TEST_DATA_DIR") + +func getTestDataDir() string { + variable := TestDataDirEnvironmentVariableName + val, ok := os.LookupEnv(variable) + if !ok { + panic( + fmt.Sprintf( + "Expected the environment variable to be set, but it was not: %s", + variable, + ), + ) + } + return val +} + +var TestDataDir = getTestDataDir() + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/deep_equal.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/deep_equal.go new file mode 100644 index 000000000..c75374b9c --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/deep_equal.go @@ -0,0 +1,239 @@ +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Perform a comparison for deep equality between `that` and `other` instance. +// +// The deep equality means that all the properties are checked for equality recursively. +func deepEqualSomeItem( + that ourtypes.ISomeItem, + other ourtypes.ISomeItem, +) bool { + thatName := that.Name() + otherName := other.Name() + if thatName != otherName { + return false + } + + thatNickname := that.Nickname() + otherNickname := other.Nickname() + if + (thatNickname == nil && otherNickname != nil) || + (thatNickname != nil && otherNickname == nil) { + return false + } + if thatNickname != nil { + if *thatNickname != *otherNickname { + return false + } + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` instance. +// +// The deep equality means that all the properties are checked for equality recursively. +func deepEqualSpecialItem( + that ourtypes.ISpecialItem, + other ourtypes.ISpecialItem, +) bool { + thatName := that.Name() + otherName := other.Name() + if thatName != otherName { + return false + } + + thatNickname := that.Nickname() + otherNickname := other.Nickname() + if + (thatNickname == nil && otherNickname != nil) || + (thatNickname != nil && otherNickname == nil) { + return false + } + if thatNickname != nil { + if *thatNickname != *otherNickname { + return false + } + } + + thatLevel := that.Level() + otherLevel := other.Level() + if thatLevel != otherLevel { + return false + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` instance. +// +// The deep equality means that all the properties are checked for equality recursively. +func deepEqualContainer( + that ourtypes.IContainer, + other ourtypes.IContainer, +) bool { + thatItems := that.Items() + otherItems := other.Items() + if !deepEqualListOf_IItem( + thatItems, + otherItems, + ) { + return false + } + + thatSomeItems := that.SomeItems() + otherSomeItems := other.SomeItems() + if !deepEqualListOf_ISomeItem( + thatSomeItems, + otherSomeItems, + ) { + return false + } + + thatSpecialItems := that.SpecialItems() + otherSpecialItems := other.SpecialItems() + if + (thatSpecialItems == nil && otherSpecialItems != nil) || + (thatSpecialItems != nil && otherSpecialItems == nil) { + return false + } + if thatSpecialItems != nil { + if !deepEqualListOf_ISpecialItem( + thatSpecialItems, + otherSpecialItems, + ) { + return false + } + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` instance. +// +// The deep equality means that all the properties are checked for equality recursively. +func deepEqualWrapper( + that ourtypes.IWrapper, + other ourtypes.IWrapper, +) bool { + thatContainer := that.Container() + otherContainer := other.Container() + if !DeepEqual( + thatContainer, + otherContainer, + ) { + return false + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` container, +// recursing into its items. +func deepEqualListOf_IItem( + that []ourtypes.IItem, + other []ourtypes.IItem, +) bool { + if len(that) != len(other) { + return false + } + + for i := range that { + if !DeepEqual( + that[i], + other[i], + ) { + return false + } + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` container, +// recursing into its items. +func deepEqualListOf_ISomeItem( + that []ourtypes.ISomeItem, + other []ourtypes.ISomeItem, +) bool { + if len(that) != len(other) { + return false + } + + for i := range that { + if !DeepEqual( + that[i], + other[i], + ) { + return false + } + } + + return true +} + +// Perform a comparison for deep equality between `that` and `other` container, +// recursing into its items. +func deepEqualListOf_ISpecialItem( + that []ourtypes.ISpecialItem, + other []ourtypes.ISpecialItem, +) bool { + if len(that) != len(other) { + return false + } + + for i := range that { + if !DeepEqual( + that[i], + other[i], + ) { + return false + } + } + + return true +} + +func DeepEqual( + that ourtypes.IClass, + other ourtypes.IClass, +) bool { + if that.ModelType() != other.ModelType() { + return false + } + + switch that.ModelType() { + case ourtypes.ModelTypeSomeItem: + return deepEqualSomeItem( + that.(ourtypes.ISomeItem), + other.(ourtypes.ISomeItem), + ) + case ourtypes.ModelTypeSpecialItem: + return deepEqualSpecialItem( + that.(ourtypes.ISpecialItem), + other.(ourtypes.ISpecialItem), + ) + case ourtypes.ModelTypeContainer: + return deepEqualContainer( + that.(ourtypes.IContainer), + other.(ourtypes.IContainer), + ) + case ourtypes.ModelTypeWrapper: + return deepEqualWrapper( + that.(ourtypes.IWrapper), + other.(ourtypes.IWrapper), + ) + } + + panic(fmt.Sprintf("Unexpected model type: %d", that.ModelType())) +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/doc.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/doc.go new file mode 100644 index 000000000..0857fea4f --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/doc.go @@ -0,0 +1,6 @@ +// Package ourtesting provides the functions and data structures shared across +// different tests. +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/filesystem.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/filesystem.go new file mode 100644 index 000000000..08ec1ec17 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/filesystem.go @@ -0,0 +1,69 @@ +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "encoding/json" + "fmt" + "io/fs" + "os" + "path/filepath" +) + +// Read the content of `pth` and parse it as a JSON. +// +// If any errors, panic. +func MustReadJsonable(pth string) (jsonable interface{}) { + bb, err := os.ReadFile(pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to read the content of: %s", + pth, + ), + ) + } + + err = json.Unmarshal(bb, &jsonable) + if err != nil { + panic( + fmt.Sprintf( + "Failed to parse the content of %s as JSON: %s", + pth, err.Error(), + ), + ) + } + return +} + +func FindFilesBySuffixRecursively(root, suffix string) []string { + var a []string + + _, statErr := os.Stat(root) + if statErr != nil { + if os.IsNotExist(statErr) { + return a + } + + panic(fmt.Sprintf("Failed to stat %s: %s", root, statErr.Error())) + } + + err := filepath.WalkDir(root, func(s string, d fs.DirEntry, e error) error { + if e != nil { + return e + } + if filepath.Ext(d.Name()) == suffix { + a = append(a, s) + } + return nil + }) + + if err != nil { + panic(fmt.Sprintf("Failed to walk %s: %s", root, err.Error())) + } + return a +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/tracing.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/tracing.go new file mode 100644 index 000000000..cec4a835d --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/ourtesting/tracing.go @@ -0,0 +1,20 @@ +package ourtesting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + ourstringification "github.com/dummy-works/dummy/stringification" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Represent `that` instance as a human-readable line of an iteration trace. +func TraceMark(that ourtypes.IClass) string { + modelTypeText := ourstringification.MustModelTypeToString( + that.ModelType(), + ) + return modelTypeText +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/reporting/reporting.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/reporting/reporting.go new file mode 100644 index 000000000..86698a098 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/reporting/reporting.go @@ -0,0 +1,240 @@ +// Package reporting provides structures and functions for reporting of errors. +package reporting + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "regexp" + "strings" + "strconv" +) + +type NameSegment struct{ + Name string +} + +type IndexSegment struct{ + Index int +} + +// Represent a member of an open JSON-able object on a path to +// the erroneous value. +// +// Unlike a [NameSegment], which names a property of one of our classes, a key +// is known only at run time, and can be any string at all. +type KeySegment struct{ + Key string +} + +type Path struct{ + // A segments is expected to be either a name segment or an index segment. + segments []interface{} + start int +} + +// Prepend the segment to the path. +// +// Grow segments exponentially if there is no place. +func (p *Path) prepend(segment interface{}) { + // See: https://en.wikipedia.org/wiki/Amortized_analysis#Dynamic_array + if len(p.segments) == 0 { + p.segments = make([]interface{}, 1) + p.segments[0] = segment + p.start = 0 + } else if p.start > 0 { + p.start-- + p.segments[p.start] = segment + } else { + s := make([]interface{}, len(p.segments) * 2) + copy(s[len(p.segments):], p.segments) + p.start = len(p.segments) - 1 + p.segments = s + p.segments[p.start] = segment + } +} + +// Prepend the name segment to the path. +func (p *Path) PrependName(segment *NameSegment) { + p.prepend(segment) +} + +// Prepend the index segment to the path. +func (p *Path) PrependIndex(segment *IndexSegment) { + p.prepend(segment) +} + +// Prepend the key segment to the path. +func (p *Path) PrependKey(segment *KeySegment) { + p.prepend(segment) +} + +// Apply the `callback` on each segment. +// +// The segment object is a [NameSegment], an [IndexSegment] or a [KeySegment]. +func (p *Path) OverSegments(callback func(interface{})) { + start := p.start + for i := start; i < len(p.segments); i++ { + callback(p.segments[i]) + } +} + +// Match the property names which a JSON path writes after a dot; the other +// names are written in brackets as JSON strings, *e.g.*, `["$eq"]`. +var variableNameRe = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z_0-9]*$`) + +// Render the path with dots and subscripts. +// +// If `bracketNonVariableNames` is set, the names which are no variable names +// are written in brackets as JSON strings. +func renderPath(p *Path, bracketNonVariableNames bool) string { + var b strings.Builder + i := 0 + p.OverSegments(func(s interface{}) { + switch v := s.(type) { + case *NameSegment: + if !bracketNonVariableNames || variableNameRe.MatchString(v.Name) { + if i > 0 { + b.WriteString(".") + } + b.WriteString(v.Name) + } else { + b.WriteString(`["`) + b.WriteString(escapeForJSONString(v.Name)) + b.WriteString(`"]`) + } + case *IndexSegment: + b.WriteString("[") + b.WriteString(strconv.Itoa(v.Index)) + b.WriteString("]") + case *KeySegment: + b.WriteString(`["`) + b.WriteString(escapeForJSONString(v.Key)) + b.WriteString(`"]`) + default: + panic( + fmt.Sprintf( + "Unexpected segment of type %T: %v", + s, s, + ), + ) + } + + i++ + }) + return b.String() +} + +// Translate the path to a JSON path. +// +// The name segments are expected to denote the names of the properties +// in JSON property names, not Golang property names. +func ToJSONPath(p *Path) string { + return renderPath(p, true) +} + +// Translate the path to a Golang access path. +// +// The name segments are expected to denote the names of the properties +// in Golang, not JSON property names. +func ToGolangPath(p *Path) string { + // NOTE(mristin): + // The name segments of a Golang path are accessors such as `Value()`, + // which we always write after a dot. + return renderPath(p, false) +} + +// Escape the characters which a JSON string may not hold as they are. +// +// The control characters without a short escape are written as Unicode +// escapes, as JSON.stringify does. +func escapeForJSONString( + text string, +) string { + var b strings.Builder + b.Grow(len(text)) + for _, r := range text { + switch r { + case '\\': + b.WriteString(`\\`) + case '"': + b.WriteString(`\"`) + case '\b': + b.WriteString(`\b`) + case '\f': + b.WriteString(`\f`) + case '\n': + b.WriteString(`\n`) + case '\r': + b.WriteString(`\r`) + case '\t': + b.WriteString(`\t`) + default: + if r < 0x20 { + fmt.Fprintf(&b, `\u%04x`, r) + } else { + b.WriteRune(r) + } + } + } + return b.String() +} + +var replacerForXPath = strings.NewReplacer( + "/", "/", + "<", "<", + ">", ">", + "\"", """, + "'", "'", +) + +// Escape special characters for XPath. +func escapeForXPath( + text string, +) string { + return replacerForXPath.Replace(text) +} + +// Generate a relative XPath based on the path segments. +// +// Leave out the leading slash (`/`). This is helpful if we +// want to embed the error report in a larger document with a prefix +// *etc.* +// +// The indices of the segments are 0-based, while the indices of XPath are +// 1-based, so we render the index `i` as `*[i + 1]`. +func ToRelativeXPath( + p *Path, +) string { + i := 0 + var b strings.Builder + p.OverSegments(func(s interface{}) { + if i > 0 { + b.WriteString("/") + } + + switch v := s.(type) { + case *NameSegment: + b.WriteString(escapeForXPath(v.Name)) + case *IndexSegment: + b.WriteString(fmt.Sprintf("*[%d]", v.Index+1)) + case *KeySegment: + // NOTE (mristin): + // A JSON-able object is written as an XML-RPC ``, which holds + // the key of a member in a `` child element, and not in + // an attribute, so the XPath has to match on that child element. + b.WriteString( + fmt.Sprintf(`member[name="%s"]`, escapeForXPath(v.Key)), + ) + default: + panic(fmt.Sprintf("Unexpected segment of type %T: %v", s, s)) + } + + i++ + }) + return b.String() +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stringification/stringification.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stringification/stringification.go new file mode 100644 index 000000000..3dd35be05 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/stringification/stringification.go @@ -0,0 +1,74 @@ +// Package stringification converts enumerations from and to string representations. +package stringification + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + ourtypes "github.com/dummy-works/dummy/types" +) + +var modelTypeFromStringMap = map[string]ourtypes.ModelType { + "SomeItem": ourtypes.ModelTypeSomeItem, + "SpecialItem": ourtypes.ModelTypeSpecialItem, + "Container": ourtypes.ModelTypeContainer, + "Wrapper": ourtypes.ModelTypeWrapper, +} + +// Parse `text` as a string representation of [ourtypes.ModelType]. +// +// If not ok, the literal result is undefined. +func ModelTypeFromString( + text string, +) (literal ourtypes.ModelType, ok bool) { + literal, ok = modelTypeFromStringMap[text] + return +} + +var modelTypeToStringArray = [...]string { + "SomeItem", + "SpecialItem", + "Container", + "Wrapper", +} + +// Translate `value` from [ourtypes.ModelType] to a string. +// +// If the value is not valid, the OK is false and the string representation is +// undefined. +func ModelTypeToString( + value ourtypes.ModelType, +) (result string, ok bool) { + i := int(value) + ok = + i >= 0 && + i < len(modelTypeToStringArray) + + if !ok { + return + } + result = modelTypeToStringArray[value] + return +} + +// Translate the `value` from [ourtypes.ModelType] to a string. +// +// Panic if the given value is invalid. +func MustModelTypeToString( + value ourtypes.ModelType, +) string { + result, ok := ModelTypeToString(value) + if !ok { + panic( + fmt.Sprintf( + "Invalid value of ModelType: %v", + value, + ), + ) + } + return result +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/common.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/common.go new file mode 100644 index 000000000..6fb2867ce --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/common.go @@ -0,0 +1,96 @@ +package types_descend_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "os" + "path/filepath" + "strings" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Trace the `instance` and compare the trace against the golden one from +// the test data, or re-record the trace if [ourtesting.RecordMode] is set. +// +// If `onlyOnce`, trace the `instance` with [ourtypes.DescendOnce]. Otherwise, +// trace with [ourtypes.Descend]. +// +// If we are comparing, and not recording, return the error message if +// the expected and the obtained trace differ. +func compareOrRerecordTrace( + instance ourtypes.IClass, + expectedPath string, + onlyOnce bool, +) (message *string) { + lines := []string{ourtesting.TraceMark(instance)} + + if onlyOnce { + instance.DescendOnce(func(descendant ourtypes.IClass) (abort bool) { + lines = append(lines, ourtesting.TraceMark(descendant)) + return + }) + } else { + instance.Descend(func(descendant ourtypes.IClass) (abort bool) { + lines = append(lines, ourtesting.TraceMark(descendant)) + return + }) + } + + got := strings.Join(lines, "\n") + + // Add a new line for POSIX systems. + got += "\n" + + if ourtesting.RecordMode { + parent := filepath.Dir(expectedPath) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + panic( + fmt.Sprintf( + "Failed to create the directory %s: %s", parent, err.Error(), + ), + ) + } + + err = os.WriteFile(expectedPath, []byte(got), 0644) + if err != nil { + panic( + fmt.Sprintf( + "Failed to write to the file %s: %s", expectedPath, err.Error(), + ), + ) + } + } else { + bb, err := os.ReadFile(expectedPath) + if err != nil { + panic( + fmt.Sprintf( + "Failed to read from file %s: %s", expectedPath, err.Error(), + ), + ) + } + + expected := string(bb) + + // NOTE (mristin): + // Git automatically strips and adds `\r`, so we have to remove it here + // to obtain a canonical text. + expected = strings.Replace(expected, "\r", "", -1) + + if expected != got { + text := fmt.Sprintf( + "What we got differs from the expected in %s. "+ + "We got:\n%s\nWe expected:\n%s", + expectedPath, got, expected, + ) + message = &text + } + } + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_once_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_once_test.go new file mode 100644 index 000000000..f97dd13ad --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_once_test.go @@ -0,0 +1,109 @@ +package types_descend_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "path/filepath" + "testing" + ourtesting "github.com/dummy-works/dummy/ourtesting" +) + +func TestDescendOnceOnAnInstanceOfSomeItem( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalSomeItem() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DescendOnce", + "SomeItem", + "maximal.json.trace", + ) + + onlyOnce := true + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnceOnAnInstanceOfSpecialItem( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalSpecialItem() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DescendOnce", + "SpecialItem", + "maximal.json.trace", + ) + + onlyOnce := true + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnceOnAnInstanceOfContainer( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalContainer() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DescendOnce", + "Container", + "maximal.json.trace", + ) + + onlyOnce := true + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnceOnAnInstanceOfWrapper( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalWrapper() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DescendOnce", + "Wrapper", + "maximal.json.trace", + ) + + onlyOnce := true + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_test.go new file mode 100644 index 000000000..c16a21652 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/descend_test/descend_test.go @@ -0,0 +1,109 @@ +package types_descend_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "path/filepath" + "testing" + ourtesting "github.com/dummy-works/dummy/ourtesting" +) + +func TestDescendOnAnInstanceOfSomeItem( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalSomeItem() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "Descend", + "SomeItem", + "maximal.json.trace", + ) + + onlyOnce := false + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnAnInstanceOfSpecialItem( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalSpecialItem() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "Descend", + "SpecialItem", + "maximal.json.trace", + ) + + onlyOnce := false + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnAnInstanceOfContainer( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalContainer() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "Descend", + "Container", + "maximal.json.trace", + ) + + onlyOnce := false + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +func TestDescendOnAnInstanceOfWrapper( + t *testing.T, +) { + instance := ourtesting.MustLoadMaximalWrapper() + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "Descend", + "Wrapper", + "maximal.json.trace", + ) + + onlyOnce := false + + message := compareOrRerecordTrace( + instance, + expectedPth, + onlyOnce, + ) + if message != nil { + t.Fatal(*message) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/is_xxx_test/is_xxx_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/is_xxx_test/is_xxx_test.go new file mode 100644 index 000000000..666f38e59 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/is_xxx_test/is_xxx_test.go @@ -0,0 +1,157 @@ +package types_is_xxx_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "testing" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" +) + +func TestIsXxxOnAnInstanceOfSomeItem(t *testing.T) { + instance := ourtesting.MustLoadMinimalSomeItem() + + if !ourtypes.IsSomeItem(instance) { + t.Errorf( + "Expected IsSomeItem to be true on an instance " + + "of ISomeItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsSpecialItem(instance) { + t.Errorf( + "Expected IsSpecialItem to be false on an instance " + + "of ISomeItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsContainer(instance) { + t.Errorf( + "Expected IsContainer to be false on an instance " + + "of ISomeItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsWrapper(instance) { + t.Errorf( + "Expected IsWrapper to be false on an instance " + + "of ISomeItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } +} + +func TestIsXxxOnAnInstanceOfSpecialItem(t *testing.T) { + instance := ourtesting.MustLoadMinimalSpecialItem() + + if !ourtypes.IsSomeItem(instance) { + t.Errorf( + "Expected IsSomeItem to be true on an instance " + + "of ISpecialItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if !ourtypes.IsSpecialItem(instance) { + t.Errorf( + "Expected IsSpecialItem to be true on an instance " + + "of ISpecialItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsContainer(instance) { + t.Errorf( + "Expected IsContainer to be false on an instance " + + "of ISpecialItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsWrapper(instance) { + t.Errorf( + "Expected IsWrapper to be false on an instance " + + "of ISpecialItem with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } +} + +func TestIsXxxOnAnInstanceOfContainer(t *testing.T) { + instance := ourtesting.MustLoadMinimalContainer() + + if ourtypes.IsSomeItem(instance) { + t.Errorf( + "Expected IsSomeItem to be false on an instance " + + "of IContainer with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsSpecialItem(instance) { + t.Errorf( + "Expected IsSpecialItem to be false on an instance " + + "of IContainer with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if !ourtypes.IsContainer(instance) { + t.Errorf( + "Expected IsContainer to be true on an instance " + + "of IContainer with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsWrapper(instance) { + t.Errorf( + "Expected IsWrapper to be false on an instance " + + "of IContainer with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } +} + +func TestIsXxxOnAnInstanceOfWrapper(t *testing.T) { + instance := ourtesting.MustLoadMinimalWrapper() + + if ourtypes.IsSomeItem(instance) { + t.Errorf( + "Expected IsSomeItem to be false on an instance " + + "of IWrapper with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsSpecialItem(instance) { + t.Errorf( + "Expected IsSpecialItem to be false on an instance " + + "of IWrapper with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if ourtypes.IsContainer(instance) { + t.Errorf( + "Expected IsContainer to be false on an instance " + + "of IWrapper with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } + + if !ourtypes.IsWrapper(instance) { + t.Errorf( + "Expected IsWrapper to be true on an instance " + + "of IWrapper with runtime type %T and with model type %v", + instance, instance.ModelType(), + ) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/types.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/types.go new file mode 100644 index 000000000..12f3ded5f --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/types.go @@ -0,0 +1,704 @@ +// Package types provides the data structures corresponding to the meta-model. +// +// Test the variance of the containers passed as arguments. +// +// A read-only `Sequence[...]` argument of a verification function is generic if its +// items are instances of a class with descendants, so that the sequences of +// the descendants can be passed to it without a copy. The arguments of the methods +// and the mutable `List[...]` arguments remain invariant. +package types + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +// Enumerate the model types for faster type switches. +// +// For example, you can use an array of function pointers to +// implement such a switch. +type ModelType int +const ( + ModelTypeSomeItem ModelType = iota + ModelTypeSpecialItem + ModelTypeContainer + ModelTypeWrapper +) + +// Represent the most general interface of the meta-model. +type IClass interface { + // Return the concrete model type at run-time. + // + // Use the model type if you want to switch on the concrete model type + // in efficient manner, as the compiler will most probably implement + // the switch in form of a jump table. + ModelType() ModelType + + // Apply the action on the instances referenced from this instance. + // + // If any of the actions returns abort `true`, the descent is immediately + // stopped, and abort `true` is also returned. Otherwise, return abort `false`. + // + // We do not recurse into the referenced instances. + // + // The action is not applied on this instance. + DescendOnce(action func(IClass) bool) (abort bool) + + // Apply the action recursively on the instances referenced from this instance. + // + // If any of the actions returns abort `true`, the descent is immediately + // stopped, and abort `true` is also returned. Otherwise, return abort `false`. + // + // The action is not applied on this instance. + Descend(action func(IClass) bool) (abort bool) +} + +type IItem interface { + IClass + + Name() string; + + SetName( + value string, + ); + + Nickname() *string; + + SetNickname( + value *string, + ); +} + +// Check whether the instance corresponds to [ourtypes.IItem] +// based on its run-time model type. +// +// The implementation uses a switch statements which is +// most probably compiled as an efficient jump table by the compiler. +func IsItem( + that IClass, +) (ok bool) { + switch that.ModelType() { + case ModelTypeSomeItem: + ok = true + case ModelTypeSpecialItem: + ok = true + } + return +} + +type ISomeItem interface { + IItem +} + +// Check whether the instance corresponds to [ourtypes.ISomeItem] +// based on its run-time model type. +// +// The implementation uses a switch statements which is +// most probably compiled as an efficient jump table by the compiler. +func IsSomeItem( + that IClass, +) (ok bool) { + switch that.ModelType() { + case ModelTypeSomeItem: + ok = true + case ModelTypeSpecialItem: + ok = true + } + return +} + +// Implements ISomeItem. +type SomeItem struct { + name string + nickname *string +} + +func (si *SomeItem) Name( +) string { + return si.name +} + +func (si *SomeItem) SetName( + value string, +) { + si.name = value +} + +func (si *SomeItem) Nickname( +) *string { + return si.nickname +} + +func (si *SomeItem) SetNickname( + value *string, +) { + si.nickname = value +} + +func (si *SomeItem) ModelType( +) ModelType { + return ModelTypeSomeItem +} + +// Apply the action on the instances referenced from si. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// We do not recurse into the referenced instances. +// +// The action is not applied on si. +func (si *SomeItem) DescendOnce( + action func(IClass) bool, +) (abort bool) { + return descend_SomeItem(si, action, false) +} + +// Apply the action recursively on the instances referenced from si. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on si. +func (si *SomeItem) Descend( + action func(IClass) bool, +) (abort bool) { + return descend_SomeItem(si, action, true) +} + +// Apply the action on the instances referenced from that, and recursively +// on their descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on that. +func descend_SomeItem( + that *SomeItem, + action func(IClass) bool, + recurse bool, +) (abort bool) { + // No descendable properties + + return +} + +// Create a new instance of SomeItem with +// the given properties. +func NewSomeItem( + name string, +) *SomeItem { + return &SomeItem{ + name: name, + nickname: nil, + } +} + +type ISpecialItem interface { + ISomeItem + + Level() int64; + + SetLevel( + value int64, + ); +} + +// Check whether the instance corresponds to [ourtypes.ISpecialItem] +// based on its run-time model type. +// +// The implementation uses a switch statements which is +// most probably compiled as an efficient jump table by the compiler. +func IsSpecialItem( + that IClass, +) (ok bool) { + ok = that.ModelType() == ModelTypeSpecialItem + return +} + +// Implements ISpecialItem. +type SpecialItem struct { + name string + nickname *string + level int64 +} + +func (si *SpecialItem) Name( +) string { + return si.name +} + +func (si *SpecialItem) SetName( + value string, +) { + si.name = value +} + +func (si *SpecialItem) Nickname( +) *string { + return si.nickname +} + +func (si *SpecialItem) SetNickname( + value *string, +) { + si.nickname = value +} + +func (si *SpecialItem) Level( +) int64 { + return si.level +} + +func (si *SpecialItem) SetLevel( + value int64, +) { + si.level = value +} + +func (si *SpecialItem) ModelType( +) ModelType { + return ModelTypeSpecialItem +} + +// Apply the action on the instances referenced from si. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// We do not recurse into the referenced instances. +// +// The action is not applied on si. +func (si *SpecialItem) DescendOnce( + action func(IClass) bool, +) (abort bool) { + return descend_SpecialItem(si, action, false) +} + +// Apply the action recursively on the instances referenced from si. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on si. +func (si *SpecialItem) Descend( + action func(IClass) bool, +) (abort bool) { + return descend_SpecialItem(si, action, true) +} + +// Apply the action on the instances referenced from that, and recursively +// on their descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on that. +func descend_SpecialItem( + that *SpecialItem, + action func(IClass) bool, + recurse bool, +) (abort bool) { + // No descendable properties + + return +} + +// Create a new instance of SpecialItem with +// the given properties. +func NewSpecialItem( + name string, + level int64, +) *SpecialItem { + return &SpecialItem{ + name: name, + level: level, + nickname: nil, + } +} + +type IContainer interface { + IClass + + Items() []IItem; + + SetItems( + value []IItem, + ); + + SomeItems() []ISomeItem; + + SetSomeItems( + value []ISomeItem, + ); + + SpecialItems() []ISpecialItem; + + SetSpecialItems( + value []ISpecialItem, + ); + + // Count the items. + // + // The arguments of the methods are never generic, so only the sequences of + // [IItem] can be passed to it, but not the sequences of + // the descendants. + CountItems( + items []IItem, + ) int64; +} + +// Check whether the instance corresponds to [ourtypes.IContainer] +// based on its run-time model type. +// +// The implementation uses a switch statements which is +// most probably compiled as an efficient jump table by the compiler. +func IsContainer( + that IClass, +) (ok bool) { + ok = that.ModelType() == ModelTypeContainer + return +} + +// Implements IContainer. +type Container struct { + items []IItem + someItems []ISomeItem + specialItems []ISpecialItem +} + +func (c *Container) Items( +) []IItem { + return c.items +} + +func (c *Container) SetItems( + value []IItem, +) { + c.items = value +} + +func (c *Container) SomeItems( +) []ISomeItem { + return c.someItems +} + +func (c *Container) SetSomeItems( + value []ISomeItem, +) { + c.someItems = value +} + +func (c *Container) SpecialItems( +) []ISpecialItem { + return c.specialItems +} + +func (c *Container) SetSpecialItems( + value []ISpecialItem, +) { + c.specialItems = value +} + +func (c *Container) ModelType( +) ModelType { + return ModelTypeContainer +} + +// Count the items. +// +// The arguments of the methods are never generic, so only the sequences of +// [IItem] can be passed to it, but not the sequences of +// the descendants. +func (c *Container) CountItems( + items []IItem, +) int64 { + return int64(len(items)) +} + +// Apply the action on the instances referenced from c. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// We do not recurse into the referenced instances. +// +// The action is not applied on c. +func (c *Container) DescendOnce( + action func(IClass) bool, +) (abort bool) { + return descend_Container(c, action, false) +} + +// Apply the action recursively on the instances referenced from c. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on c. +func (c *Container) Descend( + action func(IClass) bool, +) (abort bool) { + return descend_Container(c, action, true) +} + +// Apply the action on the instances referenced from that, and recursively +// on their descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on that. +func descend_Container( + that *Container, + action func(IClass) bool, + recurse bool, +) (abort bool) { + abort = descend_ListOf_IItem( + that.items, + action, + recurse, + ) + if abort { + return + } + + abort = descend_ListOf_ISomeItem( + that.someItems, + action, + recurse, + ) + if abort { + return + } + + if that.specialItems != nil { + abort = descend_ListOf_ISpecialItem( + that.specialItems, + action, + recurse, + ) + if abort { + return + } + } + + return +} + +// Create a new instance of Container with +// the given properties. +func NewContainer( + items []IItem, + someItems []ISomeItem, +) *Container { + return &Container{ + items: items, + someItems: someItems, + specialItems: nil, + } +} + +type IWrapper interface { + IClass + + Container() IContainer; + + SetContainer( + value IContainer, + ); +} + +// Check whether the instance corresponds to [ourtypes.IWrapper] +// based on its run-time model type. +// +// The implementation uses a switch statements which is +// most probably compiled as an efficient jump table by the compiler. +func IsWrapper( + that IClass, +) (ok bool) { + ok = that.ModelType() == ModelTypeWrapper + return +} + +// Implements IWrapper. +type Wrapper struct { + container IContainer +} + +func (w *Wrapper) Container( +) IContainer { + return w.container +} + +func (w *Wrapper) SetContainer( + value IContainer, +) { + w.container = value +} + +func (w *Wrapper) ModelType( +) ModelType { + return ModelTypeWrapper +} + +// Apply the action on the instances referenced from w. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// We do not recurse into the referenced instances. +// +// The action is not applied on w. +func (w *Wrapper) DescendOnce( + action func(IClass) bool, +) (abort bool) { + return descend_Wrapper(w, action, false) +} + +// Apply the action recursively on the instances referenced from w. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on w. +func (w *Wrapper) Descend( + action func(IClass) bool, +) (abort bool) { + return descend_Wrapper(w, action, true) +} + +// Apply the action on the instances referenced from that, and recursively +// on their descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +// +// The action is not applied on that. +func descend_Wrapper( + that *Wrapper, + action func(IClass) bool, + recurse bool, +) (abort bool) { + abort = action( + that.container, + ) + if abort { + return + } + + if recurse { + abort = that.container.Descend( + action, + ) + if abort { + return + } + } + + return +} + +// Create a new instance of Wrapper with +// the given properties. +func NewWrapper( + container IContainer, +) *Wrapper { + return &Wrapper{ + container: container, + } +} + +// Apply the action on the instances held by that, and recursively on their +// descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +func descend_ListOf_IItem( + that []IItem, + action func(IClass) bool, + recurse bool, +) (abort bool) { + for _, item := range that { + abort = action( + item, + ) + if abort { + return + } + + if recurse { + abort = item.Descend( + action, + ) + if abort { + return + } + } + } + + return +} + +// Apply the action on the instances held by that, and recursively on their +// descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +func descend_ListOf_ISomeItem( + that []ISomeItem, + action func(IClass) bool, + recurse bool, +) (abort bool) { + for _, item := range that { + abort = action( + item, + ) + if abort { + return + } + + if recurse { + abort = item.Descend( + action, + ) + if abort { + return + } + } + } + + return +} + +// Apply the action on the instances held by that, and recursively on their +// descendants if recurse is set. +// +// If any of the actions returns abort `true`, the descent is immediately +// stopped, and abort `true` is also returned. Otherwise, return abort `false`. +func descend_ListOf_ISpecialItem( + that []ISpecialItem, + action func(IClass) bool, + recurse bool, +) (abort bool) { + for _, item := range that { + abort = action( + item, + ) + if abort { + return + } + + if recurse { + abort = item.Descend( + action, + ) + if abort { + return + } + } + } + + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/xxx_or_default_test/xxx_or_default_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/xxx_or_default_test/xxx_or_default_test.go new file mode 100644 index 000000000..47baecccc --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/types/xxx_or_default_test/xxx_or_default_test.go @@ -0,0 +1,153 @@ +package types_xxx_or_default_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "path/filepath" + "fmt" + "encoding/json" + "os" + "reflect" + "strings" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Represent explicitly a literal of an enumeration. +type enumerationLiteral struct { + enumerationName string + literalName string +} + +func (el *enumerationLiteral) String() string { + return fmt.Sprintf("%s.%s", el.enumerationName, el.literalName) +} + +// Marshal the value as JSON, or panic otherwise. +func mustJSONMarshal(value interface{}) string { + bb, err := json.Marshal(value) + if err != nil { + panic( + fmt.Sprintf( + "Failed to marshal the value %v to JSON: %s", + value, err.Error(), + ), + ) + } + + return string(bb) +} + +func stringify(value interface{}) (got string) { + if value == nil { + got = mustJSONMarshal(value) + } else { + // See: https://stackoverflow.com/questions/38748098/golang-type-switch-how-to-match-a-generic-slice-array-map-chan + reflected := reflect.ValueOf(value) + + if reflected.Kind() == reflect.Slice { + parts := make([]string, reflected.Len()) + + for i := 0; i < reflected.Len(); i++ { + item := reflected.Index(i) + parts[i] = stringify(item) + } + + got = fmt.Sprintf("[%s]", strings.Join(parts, ", ")) + } else { + switch casted := value.(type) { + case bool: + got = mustJSONMarshal(casted) + case int: + got = mustJSONMarshal(casted) + case string: + got = mustJSONMarshal(casted) + case []byte: + got = fmt.Sprintf("%d byte(s)", len(casted)) + case *enumerationLiteral: + got = casted.String() + case ourtypes.IClass: + got = ourtesting.TraceMark(casted) + default: + panic( + fmt.Sprintf( + "We do not know hot to represent the value of type %T: %v", + value, value, + ), + ) + } + } + } + + return +} + +// Represent `value` such that we can immediately check whether it is the default value +// or the set one. +// +// We compare it against the recorded golden file, if not [ourtesting.RecordMode]. +// If there are differences, a `message` is set. +// +// Otherwise, when [ourtesting.RecordMode] is set, we re-record the golden file. +func compareOrRerecordValue( + value interface{}, + expectedPath string, +) (message *string) { + got := stringify(value) + + // NOTE (mristin): + // Add a new line for POSIX systems. + got += "\n" + + if ourtesting.RecordMode { + parent := filepath.Dir(expectedPath) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + panic( + fmt.Sprintf( + "Failed to create the directory %s: %s", parent, err.Error(), + ), + ) + } + + err = os.WriteFile(expectedPath, []byte(got), 0644) + if err != nil { + panic( + fmt.Sprintf( + "Failed to write to the file %s: %s", expectedPath, err.Error(), + ), + ) + } + } else { + bb, err := os.ReadFile(expectedPath) + if err != nil { + panic( + fmt.Sprintf( + "Failed to read from file %s: %s", expectedPath, err.Error(), + ), + ) + } + + expected := string(bb) + + // NOTE (mristin): + // Git automatically strips and adds `\r`, so we have to remove it here + // to obtain a canonical text. + expected = strings.Replace(expected, "\r", "", -1) + + if expected != got { + text := fmt.Sprintf( + "What we got differs from the expected in %s. " + + "We got:\n%s\nWe expected:\n%s", + expectedPath, got, expected, + ) + message = &text + } + } + + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/test/verification_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/test/verification_test.go new file mode 100644 index 000000000..4a2aef010 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/test/verification_test.go @@ -0,0 +1,692 @@ +package verification_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "testing" + ourjsonization "github.com/dummy-works/dummy/jsonization" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" + ourverification "github.com/dummy-works/dummy/verification" +) + +// Assert that there are no verification errors in the `instance` de-serialized +// from `source`. +func assertNoVerificationErrors( + t *testing.T, + instance ourtypes.IClass, + source string, +) (ok bool) { + errors := make([]*ourverification.VerificationError, 0) + ourverification.Verify( + instance, + func(err *ourverification.VerificationError) (abort bool) { + errors = append(errors, err) + return + }, + ) + + ok = true + if len(errors) > 0 { + ok = false + + var sb strings.Builder + + sb.WriteString( + fmt.Sprintf( + "Expected no errors when verifying the instance de-serialized from "+ + "%s, but got %d error(s)\n", + source, len(errors), + ), + ) + + for i, err := range errors { + sb.WriteString( + fmt.Sprintf( + "Error %d:\n%s: %s\n", + i+1, + err.PathString(), + err.Message, + ), + ) + } + + jsonable, seriaErr := ourjsonization.ToJsonable(instance) + if seriaErr != nil { + panic( + fmt.Sprintf( + "Failed to serialize instance to JSON obtained from %s: %s", + source, seriaErr.Error(), + ), + ) + } + jsonableBytes, err := json.Marshal(jsonable) + if err != nil { + panic( + fmt.Sprintf( + "Failed to marshal to JSON an instance serialized from %s: %s", + source, err.Error(), + ), + ) + } + + sb.WriteString("Instance:\n") + sb.WriteString(string(jsonableBytes)) + + t.Fatal(sb.String()) + } + return +} + +// Assert that either the verification errors match the recorded ones at `pth`, if +// [ourtesting.RecordMode] is set, or re-record the verification errors at `pth`. +func assertEqualsExpectedOrRerecordVerificationErrors( + t *testing.T, + errors []*ourverification.VerificationError, + source string, + expectedPth string, +) (ok bool) { + ok = true + if len(errors) == 0 { + ok = false + t.Fatalf( + "Expected at least one verification error, "+ + "but got none when verifying the model loaded from: %s", + source, + ) + } + + parts := make([]string, len(errors)) + for i, verErr := range errors { + parts[i] = fmt.Sprintf( + "%s: %s", + verErr.PathString(), + verErr.Message, + ) + } + + // Add a newline for POSIX systems + got := strings.Replace(strings.Join(parts, ";\n"), "\r\n", "\n", -1) + "\n" + + if ourtesting.RecordMode { + parent := filepath.Dir(expectedPth) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + panic( + fmt.Sprintf( + "Failed to create the directory %s: %s", parent, err.Error(), + ), + ) + } + + err = os.WriteFile(expectedPth, []byte(got), 0644) + if err != nil { + ok = false + t.Fatalf("Failed to write to %s: %s", expectedPth, err.Error()) + return + } + } else { + _, err := os.Stat(expectedPth) + if err != nil { + ok = false + t.Fatalf( + "Failed to stat the file %s: %s; if the file does not exist, "+ + "you probably want to record the test data by "+ + "setting the environment variable %s", + expectedPth, err.Error(), ourtesting.RecordModeEnvironmentVariableName, + ) + return + } + + var bb []byte + bb, err = os.ReadFile(expectedPth) + if err != nil { + ok = false + t.Fatalf("Failed to read from %s: %s", expectedPth, err.Error()) + return + } + expected := strings.ReplaceAll(string(bb), "\r\n", "\n") + + if expected != got { + ok = false + t.Fatalf( + "The expected verification errors (read from %s) in the model "+ + "de-serialized from %s do not match the obtained ones. "+ + "Expected:\n%s\nGot:\n%s", + expectedPth, source, expected, got, + ) + return + } + } + return +} + +func TestSomeItemOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "SomeItem", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(veriErr *ourverification.VerificationError) (abort bool) { + errors = append(errors, veriErr) + return + }, + ) + + ok := assertNoVerificationErrors( + t, + deserialized, + pth, + ) + if !ok { + return + } + } +} + +func TestSomeItemFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Invalid", + "*", // This asterisk represents the cause. + "SomeItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "VerificationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".errors", + ) + + deserialized, deseriaErr := ourjsonization.SomeItemFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(err *ourverification.VerificationError) (abort bool) { + errors = append(errors, err) + return + }, + ) + + ok := assertEqualsExpectedOrRerecordVerificationErrors( + t, + errors, + pth, + expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestSpecialItemOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "SpecialItem", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(veriErr *ourverification.VerificationError) (abort bool) { + errors = append(errors, veriErr) + return + }, + ) + + ok := assertNoVerificationErrors( + t, + deserialized, + pth, + ) + if !ok { + return + } + } +} + +func TestSpecialItemFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Invalid", + "*", // This asterisk represents the cause. + "SpecialItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "VerificationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".errors", + ) + + deserialized, deseriaErr := ourjsonization.SpecialItemFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(err *ourverification.VerificationError) (abort bool) { + errors = append(errors, err) + return + }, + ) + + ok := assertEqualsExpectedOrRerecordVerificationErrors( + t, + errors, + pth, + expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestContainerOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "Container", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.ContainerFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(veriErr *ourverification.VerificationError) (abort bool) { + errors = append(errors, veriErr) + return + }, + ) + + ok := assertNoVerificationErrors( + t, + deserialized, + pth, + ) + if !ok { + return + } + } +} + +func TestContainerFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Invalid", + "*", // This asterisk represents the cause. + "Container", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "VerificationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".errors", + ) + + deserialized, deseriaErr := ourjsonization.ContainerFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(err *ourverification.VerificationError) (abort bool) { + errors = append(errors, err) + return + }, + ) + + ok := assertEqualsExpectedOrRerecordVerificationErrors( + t, + errors, + pth, + expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestWrapperOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Json", + "Expected", + "Wrapper", + ), + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + deserialized, deseriaErr := ourjsonization.WrapperFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(veriErr *ourverification.VerificationError) (abort bool) { + errors = append(errors, veriErr) + return + }, + ) + + ok := assertNoVerificationErrors( + t, + deserialized, + pth, + ) + if !ok { + return + } + } +} + +func TestWrapperFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Json", + "Unexpected", + "Invalid", + "*", // This asterisk represents the cause. + "Wrapper", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".json", + ) + sort.Strings(pths) + + for _, pth := range pths { + jsonable := ourtesting.MustReadJsonable( + pth, + ) + + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "VerificationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".errors", + ) + + deserialized, deseriaErr := ourjsonization.WrapperFromJsonable( + jsonable, + ) + if deseriaErr != nil { + t.Fatalf( + "Unexpected deserialization error from %s: %s", + pth, deseriaErr.Error(), + ) + return + } + + var errors []*ourverification.VerificationError + ourverification.Verify( + deserialized, + func(err *ourverification.VerificationError) (abort bool) { + errors = append(errors, err) + return + }, + ) + + ok := assertEqualsExpectedOrRerecordVerificationErrors( + t, + errors, + pth, + expectedPth, + ) + if !ok { + return + } + } + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/verification.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/verification.go new file mode 100644 index 000000000..8da690bd3 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/verification/verification.go @@ -0,0 +1,863 @@ +// Package verification allows you to verify model instances. +// +// The main function is [Verify]. +// +// Other verification functions (`Verify*`) are left for modularity, in case you want +// to be explicit about the typing in your code. However, in the large majority of +// the cases, you only want to call [Verify]. +package verification + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + ourcommon "github.com/dummy-works/dummy/common" + ourreporting "github.com/dummy-works/dummy/reporting" + ourtypes "github.com/dummy-works/dummy/types" +) + +// Represent a verification violation. +// +// Implements `error`. +type VerificationError struct{ + Path *ourreporting.Path + Message string +} + +func newVerificationError(message string) *VerificationError { + return &VerificationError{ + Path: &ourreporting.Path{}, + Message: message, + } +} + +func (ve *VerificationError) Error() string { + return fmt.Sprintf( + "%s: %s", + ve.PathString(), + ve.Message, + ) +} + +// Render the path as a string. +func (ve *VerificationError) PathString() string { + return ourreporting.ToGolangPath(ve.Path) +} + +// Check that the names of the items are unique. +// +// The items are a generic sequence, so the sequences of +// the descendants of [ourtypes.IItem] can be passed without a copy. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func NamesAreUnique[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + var nameSet map[string]struct{} = make(map[string]struct{}) + for _, item := range items { + if ( + ourcommon.MapContains( + nameSet, + item.Name(), + )) { + return false + } + nameSet[item.Name()] = struct{}{} + } + return true +} + +// Iterate over a generic sequence in a generator. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func NamesAreNonEmpty[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + return ourcommon.All( + func(item ItemsT) bool { + return ourcommon.LenStr(item.Name()) > 0 + }, + items, + ) +} + +// Narrow the items of a generic sequence in a generator. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func SomeSpecialItemHasLevel[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + return ourcommon.Some( + func(item ItemsT) bool { + return ourtypes.IsSpecialItem(item) && + any(item).(ourtypes.ISpecialItem).Level() > 0 + }, + items, + ) +} + +// Index a generic sequence, and narrow the item. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func FirstIsSpecial[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + if len(items) == 0 { + return false + } + first := items[0] + if ourtypes.IsSpecialItem(first) { + return any(first).(ourtypes.ISpecialItem).Level() > 0 + } + return false +} + +// Check that the nicknames of the items are unique, if specified. +// +// This mirrors the body of `ID_shorts_are_unique` in aas-core-meta V3.2: +// a local variable takes an optional property of an item of a generic sequence, +// and the loop continues if it is None. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func NicknamesAreUnique[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + var nicknameSet map[string]struct{} = make(map[string]struct{}) + for _, item := range items { + nickname := item.Nickname() + if nickname == nil { + continue + } + if ( + ourcommon.MapContains( + nicknameSet, + *nickname, + )) { + return false + } + nicknameSet[*nickname] = struct{}{} + } + return true +} + +// Narrow the loop variable over a generic sequence in a for-loop. +// +// The loop variable is of the type parameter in Go, so the down-cast needs to +// go through `any(...)`, and of the template parameter in C++. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func LevelsOfSpecialItemsArePositive[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + for _, item := range items { + if ourtypes.IsSpecialItem(item) { + if any(item).(ourtypes.ISpecialItem).Level() <= 0 { + return false + } + } + } + return true +} + +// Narrow an immutable local variable which holds an item of a generic sequence. +// +// In contrast to [FirstIsSpecial], the local variable is annotated, so +// it is of the parent class instead of the type parameter. Hence, Go down-casts +// it without `any(...)`, and C++ binds the item to a constant reference to +// the shared pointer of the parent class. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func LastIsSpecial[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + if len(items) == 0 { + return false + } + var last ourtypes.IItem = items[len(items) - 1] + if ourtypes.IsSpecialItem(last) { + return last.(ourtypes.ISpecialItem).Level() > 0 + } + return false +} + +// Mix two generic sequences with an argument which is not generic. +// +// The names must differ, except for the sharedName, which may repeat. +// +// Go declares two type parameters, and C++ two template parameters. The nested +// generators iterate over two different generic sequences, so their variables +// are of two different type parameters. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +// +// The items of the argument otherItems are of the type parameter OtherItemsT instead of ourtypes.ISomeItem so that the slices of the descendants of ourtypes.ISomeItem can be passed without a copy, as the slices are invariant in Go. +func NamesAreDisjoint[ItemsT ourtypes.IItem, OtherItemsT ourtypes.ISomeItem]( + items []ItemsT, + otherItems []OtherItemsT, + sharedName string, +) bool { + return ourcommon.All( + func(item ItemsT) bool { + return ourcommon.All( + func(otherItem OtherItemsT) bool { + return item.Name() != otherItem.Name() || + item.Name() == sharedName + }, + otherItems, + ) + }, + items, + ) +} + +// Use the immutable aliases of the generic sequences. +// +// An immutable alias of the very same type shares the type annotation of +// the aliased generic sequence, so it is a generic sequence itself. Go declares +// the alias with the type parameter, C++ with the template parameter, while C# +// and Java infer its type with `var`. The `alias_of_alias` aliases an alias, +// and the `optional_alias` aliases an optional generic sequence. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +// +// The items of the argument optionalItems are of the type parameter OptionalItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func AliasedNamesAreUnique[ItemsT ourtypes.IItem, OptionalItemsT ourtypes.IItem]( + items []ItemsT, + optionalItems []OptionalItemsT, +) bool { + var alias []ItemsT = items + var aliasOfAlias []ItemsT = alias + var optionalAlias []OptionalItemsT = optionalItems + for _, item := range alias { + if ourtypes.IsSpecialItem(item) { + if any(item).(ourtypes.ISpecialItem).Level() <= 0 { + return false + } + } + } + return ourcommon.All( + func(item ItemsT) bool { + return ourcommon.LenStr(item.Name()) > 0 + }, + aliasOfAlias, + ) && + NamesAreUnique(aliasOfAlias) && + OptionalNamesAreUnique(optionalAlias) && + ((optionalAlias == nil) || + NamesAreUnique(optionalAlias)) +} + +// Look up an instance of a descendant in a generic sequence. +// +// Go compares the instances as `any`'s, as the items are of the type parameter, +// and C++ converts the member to the shared pointer of [ourtypes.IItem] so that +// the raw pointers are comparable for any template parameter. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func ContainsSomeItem[ItemsT ourtypes.IItem]( + someItem ourtypes.ISomeItem, + items []ItemsT, +) bool { + return ourcommon.SliceContainsInstance( + items, + someItem, + ) +} + +// Look up the items of a generic sequence in another generic sequence. +// +// The member and the items are of two different type parameters. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +// +// The items of the argument otherItems are of the type parameter OtherItemsT instead of ourtypes.ISomeItem so that the slices of the descendants of ourtypes.ISomeItem can be passed without a copy, as the slices are invariant in Go. +func ItemsOverlap[ItemsT ourtypes.IItem, OtherItemsT ourtypes.ISomeItem]( + items []ItemsT, + otherItems []OtherItemsT, +) bool { + return ourcommon.Some( + func(otherItem OtherItemsT) bool { + return ourcommon.SliceContainsInstance( + items, + otherItem, + ) + }, + otherItems, + ) +} + +// Look up an instance in a sequence which is not generic. +// +// The [ourtypes.ISpecialItem] has no descendants, so the items are not +// a generic sequence, but Go still compares the instances as `any`'s. +func ContainsSpecialItem( + specialItem ourtypes.ISpecialItem, + items []ourtypes.ISpecialItem, +) bool { + return ourcommon.SliceContainsInstance( + items, + specialItem, + ) +} + +// Pass the local variables on to generic sequences. +// +// The `some_items` is a read-only alias without an annotation, while +// the `all_some_items` is an immutable local variable declared with +// `Final[...]`. Both hold the sequences of [ourtypes.ISomeItem], and are passed +// on to the generic sequences of [ourtypes.IItem], required as well as optional. +func ContainerNamesAreUnique( + container ourtypes.IContainer, +) bool { + someItems := container.SomeItems() + var allSomeItems []ourtypes.ISomeItem = container.SomeItems() + return NamesAreUnique(someItems) && + NamesAreUnique(allSomeItems) && + OptionalNamesAreUnique(allSomeItems) +} + +// Pass an optional generic sequence on to a generic sequence. +// +// The items of the argument items are of the type parameter ItemsT instead of ourtypes.IItem so that the slices of the descendants of ourtypes.IItem can be passed without a copy, as the slices are invariant in Go. +func OptionalNamesAreUnique[ItemsT ourtypes.IItem]( + items []ItemsT, +) bool { + return (items == nil) || + NamesAreUnique(items) +} + +// Pass the generic sequences of the descendants on. +// +// The someItems are passed on as a required and as an optional +// sequence of the parent class, while the specialItems are passed on +// as an optional sequence and, once narrowed, as a required sequence of the parent +// class of the parent class. +// +// The items of the argument someItems are of the type parameter SomeItemsT instead of ourtypes.ISomeItem so that the slices of the descendants of ourtypes.ISomeItem can be passed without a copy, as the slices are invariant in Go. +func SomeNamesAreUnique[SomeItemsT ourtypes.ISomeItem]( + someItems []SomeItemsT, + specialItems []ourtypes.ISpecialItem, +) bool { + return NamesAreUnique(someItems) && + OptionalNamesAreUnique(someItems) && + OptionalNamesAreUnique(specialItems) && + OptionalNamesAreUnique([]ourtypes.IItem(nil)) && + ((specialItems == nil) || + NamesAreUnique(specialItems)) +} + +// Check that the levels are positive. +// +// The [ourtypes.ISpecialItem] has no descendants, so the items are not +// a generic sequence. +func LevelsArePositive( + items []ourtypes.ISpecialItem, +) bool { + return ourcommon.All( + func(item ourtypes.ISpecialItem) bool { + return item.Level() > 0 + }, + items, + ) +} + +// Verify `that` instance of [ourtypes.ISomeItem]. +// +// You have to supply the callback `onError` to iterate over the errors. +// If `onError` returns abort `true`, this function will abort +// further verification as well, and return abort `true`. Otherwise, +// abort `false` is returned. +func VerifySomeItem( + that ourtypes.ISomeItem, + onError func(*VerificationError) bool, +) (abort bool) { + abort = false + + // No verification has been defined for ISomeItem. + + return +} + +// Verify `that` instance of [ourtypes.ISpecialItem]. +// +// You have to supply the callback `onError` to iterate over the errors. +// If `onError` returns abort `true`, this function will abort +// further verification as well, and return abort `true`. Otherwise, +// abort `false` is returned. +func VerifySpecialItem( + that ourtypes.ISpecialItem, + onError func(*VerificationError) bool, +) (abort bool) { + abort = false + + // No verification has been defined for ISpecialItem. + + return +} + +// Verify `that` instance of [ourtypes.IContainer]. +// +// You have to supply the callback `onError` to iterate over the errors. +// If `onError` returns abort `true`, this function will abort +// further verification as well, and return abort `true`. Otherwise, +// abort `false` is returned. +func VerifyContainer( + that ourtypes.IContainer, + onError func(*VerificationError) bool, +) (abort bool) { + abort = false + + if !( + !(that.SpecialItems() != nil) || + ourcommon.All( + func(specialItem ourtypes.ISpecialItem) bool { + return ContainsSpecialItem( + specialItem, + that.SpecialItems(), + ) + }, + that.SpecialItems(), + )) { + abort = onError( + newVerificationError( + "Each special item must be among the special items.",), + ) + if abort { + return + } + } + + if !( + len(that.SomeItems()) == 0 || + ItemsOverlap(that.Items(), that.SomeItems())) { + abort = onError( + newVerificationError( + "If there are some items, they must overlap with the items.",), + ) + if abort { + return + } + } + + if !( + ourcommon.All( + func(someItem ourtypes.ISomeItem) bool { + return ContainsSomeItem(someItem, that.Items()) + }, + that.SomeItems(), + )) { + abort = onError( + newVerificationError( + "Some items must be among the items.",), + ) + if abort { + return + } + } + + if !( + AliasedNamesAreUnique( + that.SomeItems(), + that.SpecialItems(), + )) { + abort = onError( + newVerificationError( + "Names of some items and of special items must be unique " + + "through aliases.", + ), + ) + if abort { + return + } + } + + if !( + NamesAreDisjoint( + that.Items(), + that.SomeItems(), + "x", + )) { + abort = onError( + newVerificationError( + "Names of the items and of some items must be disjoint.",), + ) + if abort { + return + } + } + + if !( + !SomeSpecialItemHasLevel(that.Items()) || + LastIsSpecial(that.Items())) { + abort = onError( + newVerificationError( + "If there is a special item with level, the last item must " + + "be special.", + ), + ) + if abort { + return + } + } + + if !LevelsOfSpecialItemsArePositive(that.Items()) { + abort = onError( + newVerificationError( + "Levels of the special items among the items must be " + + "positive.", + ), + ) + if abort { + return + } + } + + if !NicknamesAreUnique(that.SomeItems()) { + abort = onError( + newVerificationError( + "Nicknames of some items must be unique.",), + ) + if abort { + return + } + } + + if !( + that.CountItems(that.Items()) == int64(len(that.Items()))) { + abort = onError( + newVerificationError( + "Count of the items must match the number of the items.",), + ) + if abort { + return + } + } + + if !OptionalNamesAreUnique(that.SpecialItems()) { + abort = onError( + newVerificationError( + "Names of the special items must be unique.",), + ) + if abort { + return + } + } + + if !( + !(that.SpecialItems() != nil) || + FirstIsSpecial(that.SpecialItems())) { + abort = onError( + newVerificationError( + "The first special item must be special.",), + ) + if abort { + return + } + } + + if !( + !(that.SpecialItems() != nil) || + LevelsArePositive(that.SpecialItems())) { + abort = onError( + newVerificationError( + "Levels of the special items must be positive.",), + ) + if abort { + return + } + } + + if !( + !SomeSpecialItemHasLevel(that.Items()) || + FirstIsSpecial(that.Items())) { + abort = onError( + newVerificationError( + "If there is a special item with level, the first item must " + + "be special.", + ), + ) + if abort { + return + } + } + + if !NamesAreNonEmpty(that.SomeItems()) { + abort = onError( + newVerificationError( + "Names of some items must be non-empty.",), + ) + if abort { + return + } + } + + if !( + SomeNamesAreUnique( + that.SomeItems(), + that.SpecialItems(), + )) { + abort = onError( + newVerificationError( + "Names of some items and of special items must be unique.",), + ) + if abort { + return + } + } + + if !NamesAreUnique(that.Items()) { + abort = onError( + newVerificationError( + "Names of the items must be unique.",), + ) + if abort { + return + } + } + + if that.Items() == nil { + abort = onError( + newVerificationError( + "Required property not set: Items", + ), + ) + if abort { + return + } + } else { + abort = verifyListOf_IItem( + that.Items(), + func(err *VerificationError) bool { + err.Path.PrependName( + &ourreporting.NameSegment{ + Name: "Items", + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + if that.SomeItems() == nil { + abort = onError( + newVerificationError( + "Required property not set: SomeItems", + ), + ) + if abort { + return + } + } else { + abort = verifyListOf_ISomeItem( + that.SomeItems(), + func(err *VerificationError) bool { + err.Path.PrependName( + &ourreporting.NameSegment{ + Name: "SomeItems", + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + if that.SpecialItems() != nil { + abort = verifyListOf_ISpecialItem( + that.SpecialItems(), + func(err *VerificationError) bool { + err.Path.PrependName( + &ourreporting.NameSegment{ + Name: "SpecialItems", + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + return +} + +// Verify `that` instance of [ourtypes.IWrapper]. +// +// You have to supply the callback `onError` to iterate over the errors. +// If `onError` returns abort `true`, this function will abort +// further verification as well, and return abort `true`. Otherwise, +// abort `false` is returned. +func VerifyWrapper( + that ourtypes.IWrapper, + onError func(*VerificationError) bool, +) (abort bool) { + abort = false + + if !ContainerNamesAreUnique(that.Container()) { + abort = onError( + newVerificationError( + "Names of some items of the container must be unique.",), + ) + if abort { + return + } + } + + if that.Container() == nil { + abort = onError( + newVerificationError( + "Required property not set: Container", + ), + ) + if abort { + return + } + } else { + abort = Verify( + that.Container(), + func(err *VerificationError) bool { + err.Path.PrependName( + &ourreporting.NameSegment{ + Name: "Container", + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + return +} + +// Verify the items of `that` recursively. +func verifyListOf_IItem( + that []ourtypes.IItem, + onError func(*VerificationError) bool, +) (abort bool) { + for i, item := range that { + abort = Verify( + item, + func(err *VerificationError) bool { + err.Path.PrependIndex( + &ourreporting.IndexSegment{ + Index: i, + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + return +} + +// Verify the items of `that` recursively. +func verifyListOf_ISomeItem( + that []ourtypes.ISomeItem, + onError func(*VerificationError) bool, +) (abort bool) { + for i, item := range that { + abort = Verify( + item, + func(err *VerificationError) bool { + err.Path.PrependIndex( + &ourreporting.IndexSegment{ + Index: i, + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + return +} + +// Verify the items of `that` recursively. +func verifyListOf_ISpecialItem( + that []ourtypes.ISpecialItem, + onError func(*VerificationError) bool, +) (abort bool) { + for i, item := range that { + abort = Verify( + item, + func(err *VerificationError) bool { + err.Path.PrependIndex( + &ourreporting.IndexSegment{ + Index: i, + }, + ) + return onError(err) + }, + ) + if abort { + return + } + } + + return +} + +// Verify ``that`` instance. +// +// You have to supply the callback `onError` to iterate over the errors. +// If `onError` returns abort `true`, this function will abort +// further verification as well, and return abort `true`. Otherwise, +// abort `false` is returned. +func Verify( + that ourtypes.IClass, + onError func(*VerificationError) bool, +) (abort bool) { + modelType := that.ModelType() + switch modelType { + case ourtypes.ModelTypeSomeItem: + abort = VerifySomeItem( + that.(ourtypes.ISomeItem), + onError, + ) + case ourtypes.ModelTypeSpecialItem: + abort = VerifySpecialItem( + that.(ourtypes.ISpecialItem), + onError, + ) + case ourtypes.ModelTypeContainer: + abort = VerifyContainer( + that.(ourtypes.IContainer), + onError, + ) + case ourtypes.ModelTypeWrapper: + abort = VerifyWrapper( + that.(ourtypes.IWrapper), + onError, + ) + default: + abort = onError( + newVerificationError( + fmt.Sprintf( + "Unexpected model type literal: %v", + modelType, + ), + ), + ) + } + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/common_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/common_test.go new file mode 100644 index 000000000..f092c4c7d --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/common_test.go @@ -0,0 +1,296 @@ +package xmlization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + "testing" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourxmlization "github.com/dummy-works/dummy/xmlization" +) + +// Assert that there is no de-serialization error when de-serializing +// from `source`. +func assertNoDeserializationError( + t *testing.T, + err error, + source string, +) (ok bool) { + ok = true + if err != nil { + ok = false + t.Fatalf( + "Expected no de-serialization error from %s, but got: %s", + source, err.Error(), + ) + return + } + return +} + +// Assert that there is no serialization error when serializing the instance +// originally coming from `source`. +func assertNoSerializationError( + t *testing.T, + err error, + source string, +) (ok bool) { + ok = true + if err != nil { + ok = false + t.Fatalf( + "Expected no serialization error when serializing "+ + "the instance obtained from %s, but got: %s", + source, err.Error(), + ) + return + } + return +} + +// NOTE (mristin): +// Currently, Go does not support self-closing tags, +// see: https://github.com/golang/go/issues/21399. +// We apply the following hack to make the tags self-closing even if they are not. +// This is unsafe in general, but works OK for the limited set of test data that we are +// here dealing with. +// +// The code has been taken from: https://github.com/golang/go/issues/21399#issuecomment-1342730174 + +// NOTE (mristin): +// The opening tag, tag name plus an optional attribute list (*e.g.*, +// ``xmlns="..."`` on a root element), is captured in the first (and only) +// submatch group so that we do not lose any attributes when we force +// the tag to be self-closing below. The attribute list must not end with +// a slash, as we would otherwise match an already self-closing tag followed +// by the closing tag of its parent, *e.g.*, ````. +var emptyTagRe = regexp.MustCompile(`<(\w+(?:\s[^>]*[^/>])?)>`) + +func forceSelfClosingTags(text string) string { + b := []byte(text) + emptyTagIdxs := emptyTagRe.FindAllSubmatchIndex(b, -1) + + if len(emptyTagIdxs) == 0 { + return text + } + + // NOTE (mristin): + // The matches are walked in order, and the pieces of `b` between them are + // copied over as they are. Mind that `nb` starts out empty and that `b` is + // only ever *read*: an earlier version appended into `b[0:idx[0]]`, which + // writes into `b`'s own backing array, so a document with more than one + // empty tag came out garbled -- every turn of the loop then indexed into + // a `b` which the previous turn had already overwritten. + var nb []byte + previous := 0 + + for _, idx := range emptyTagIdxs { + // Copy everything since the previous match, then the tag itself -- + // its name and its attributes -- as a self-closing one. + nb = append(nb, b[previous:idx[0]]...) + nb = append(nb, fmt.Sprintf("<%s/>", b[idx[2]:idx[3]])...) + + previous = idx[1] + } + + // Finally, copy everything after the last match. + nb = append(nb, b[previous:]...) + + return string(nb) +} + +var whitespaceBetweenTagsRe = regexp.MustCompile(`>\s+<`) + +// Remove all whitespace (including newlines, tabs, and spaces) between XML tags +// without parsing the XML. +func removeWhitespaceBetweenTags(text string) string { + return whitespaceBetweenTagsRe.ReplaceAllString(text, "><") +} + +var whitespaceBeforeAngleBracketsRe = regexp.MustCompile(`\s+>`) +var whitespaceBeforeSelfClosingRe = regexp.MustCompile(`\s+/>`) +var whitespaceAfterAngleBracketsRe = regexp.MustCompile(`<\s+`) + +// Remove any whitespace before `>` and `/>` or after `<` +func removeWhitespaceWithinTags(text string) string { + result := text + result = whitespaceBeforeAngleBracketsRe.ReplaceAllString(result, ">") + result = whitespaceBeforeSelfClosingRe.ReplaceAllString(result, "/>") + result = whitespaceAfterAngleBracketsRe.ReplaceAllString(result, "<") + return result +} + +// Assert that the serialization `other`, as XML document, equals the original +// +// XML document `that` read from the `source`. +func assertSerializationEqualsDeserialization( + t *testing.T, + that string, + other string, + source string, +) (ok bool) { + ok = true + + // Remove carriers to avoid problems between Windows, Posix and MacOS + canonicalThat := strings.ReplaceAll(that, "\r", "") + canonicalOther := strings.ReplaceAll(other, "\r", "") + + canonicalThat = strings.TrimSpace(canonicalThat) + canonicalOther = strings.TrimSpace(canonicalOther) + + canonicalThat = forceSelfClosingTags(canonicalThat) + canonicalOther = forceSelfClosingTags(canonicalOther) + + // NOTE (mristin): + // The following hack is SUPER ugly and unsafe! However, it works. Given Go's + // limited support for XML, we gave up on a safer approach :(. We tested + // the following approaches before applying this hack: + // * A round-trip over `encoding/xml`. Failed due to + // https://github.com/golang/go/issues/13400. + // * Using `aqwari.net/xml/xmltree`. Failed as the special characters in the + // element content still has not been de-escaped or consistently escaped in + // a round trip. + + canonicalThat = strings.ReplaceAll(canonicalThat, "'", "'") + canonicalOther = strings.ReplaceAll(canonicalOther, "'", "'") + + canonicalThat = removeWhitespaceBetweenTags(canonicalThat) + canonicalOther = removeWhitespaceBetweenTags(canonicalOther) + + canonicalThat = removeWhitespaceWithinTags(canonicalThat) + canonicalOther = removeWhitespaceWithinTags(canonicalOther) + + thatLines := strings.Split(canonicalThat, "\n") + otherLines := strings.Split(canonicalOther, "\n") + + if canonicalThat != canonicalOther { + b := new(strings.Builder) + minLines := len(thatLines) + if minLines > len(otherLines) { + minLines = len(otherLines) + } + for i := 0; i < minLines; i++ { + if thatLines[i] == otherLines[i] { + b.WriteString(fmt.Sprintf(" %s\n", thatLines[i])) + } else { + b.WriteString(fmt.Sprintf("ORIGINAL %s\n", thatLines[i])) + b.WriteString(fmt.Sprintf("SERIALIZED %s\n", otherLines[i])) + break + } + } + + ok = false + t.Fatalf( + "The canonicalized XML serialization of the de-serialized instance "+ + "from %s does not equal the canonicalized original XML document:\n"+ + "%s", + source, b.String(), + ) + return + } + + return +} + +// Assert that there is a de-serialization error. +// +// If [ourtesting.RecordMode] is set, the de-serialization error is re-recorded +// to `expectedPth`. Otherwise, the error is compared against the golden file +// `expectedPth`. +func assertIsDeserializationErrorAndEqualsExpectedOrRecord( + t *testing.T, + err error, + source string, + expectedPth string, +) (ok bool) { + ok = true + + if err == nil { + ok = false + t.Fatalf("De-serialization error expected from %s, but got none", source) + return + } + + deseriaErr, is := err.(*ourxmlization.DeserializationError) + if !is { + ok = false + t.Fatalf( + "Expected a de-serialization error, "+ + "but got an error of type %T from %s: %v", + err, source, err, + ) + return + } + + // Add a new line for POSIX systems. + got := deseriaErr.Error() + "\n" + + if ourtesting.RecordMode { + parent := filepath.Dir(expectedPth) + err := os.MkdirAll(parent, os.ModePerm) + if err != nil { + panic( + fmt.Sprintf( + "Failed to create the directory %s: %s", parent, err.Error(), + ), + ) + } + + err = os.WriteFile(expectedPth, []byte(got), 0644) + if err != nil { + panic( + fmt.Sprintf( + "Failed to write to the file %s: %s", expectedPth, err.Error(), + ), + ) + } + } else { + _, err := os.Stat(expectedPth) + if err != nil { + ok = false + t.Fatalf( + "Failed to stat the file %s: %s; if the file does not exist, "+ + "you probably want to record the test data by "+ + "setting the environment variable %s", + expectedPth, err.Error(), ourtesting.RecordModeEnvironmentVariableName, + ) + return + } + + bb, err := os.ReadFile(expectedPth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to read from file %s: %s", expectedPth, err.Error(), + ), + ) + } + + expected := string(bb) + + // NOTE (mristin): + // Git automatically strips and adds `\r`, so we have to remove it here + // to obtain a canonical text. + expected = strings.Replace(expected, "\r", "", -1) + + if expected != got { + ok = false + t.Fatalf( + "What we got differs from the expected in %s. "+ + "We got:\n%s\nWe expected:\n%s", + expectedPth, got, expected, + ) + return + } + } + + return +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/concrete_classes_test.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/concrete_classes_test.go new file mode 100644 index 000000000..2b31e99cd --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/test/concrete_classes_test.go @@ -0,0 +1,582 @@ +package xmlization_test + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "bytes" + "path/filepath" + "fmt" + "os" + "sort" + "strings" + "testing" + "encoding/xml" + ourtesting "github.com/dummy-works/dummy/ourtesting" + ourtypes "github.com/dummy-works/dummy/types" + ourxmlization "github.com/dummy-works/dummy/xmlization" +) + +func TestSomeItemRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Expected", + "someItem", + ), + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + deserialized, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + if _, ok := deserialized.(ourtypes.ISomeItem); !ok { + t.Fatalf( + "Expected an instance of ISomeItem, "+ + "but got %T: %v", + deserialized, deserialized, + ) + return + } + + buf := &bytes.Buffer{} + encoder := xml.NewEncoder(buf) + encoder.Indent("", "\t") + + seriaErr := ourxmlization.Marshal(encoder, deserialized, true) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + roundTrip := string(buf.Bytes()) + + ok = assertSerializationEqualsDeserialization( + t, + text, + roundTrip, + pth, + ) + if !ok { + return + } + } +} + +func TestSomeItemDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "someItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + _, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertIsDeserializationErrorAndEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestSpecialItemRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Expected", + "specialItem", + ), + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + deserialized, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + if _, ok := deserialized.(ourtypes.ISpecialItem); !ok { + t.Fatalf( + "Expected an instance of ISpecialItem, "+ + "but got %T: %v", + deserialized, deserialized, + ) + return + } + + buf := &bytes.Buffer{} + encoder := xml.NewEncoder(buf) + encoder.Indent("", "\t") + + seriaErr := ourxmlization.Marshal(encoder, deserialized, true) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + roundTrip := string(buf.Bytes()) + + ok = assertSerializationEqualsDeserialization( + t, + text, + roundTrip, + pth, + ) + if !ok { + return + } + } +} + +func TestSpecialItemDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "specialItem", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + _, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertIsDeserializationErrorAndEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestContainerRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Expected", + "container", + ), + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + deserialized, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + if _, ok := deserialized.(ourtypes.IContainer); !ok { + t.Fatalf( + "Expected an instance of IContainer, "+ + "but got %T: %v", + deserialized, deserialized, + ) + return + } + + buf := &bytes.Buffer{} + encoder := xml.NewEncoder(buf) + encoder.Indent("", "\t") + + seriaErr := ourxmlization.Marshal(encoder, deserialized, true) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + roundTrip := string(buf.Bytes()) + + ok = assertSerializationEqualsDeserialization( + t, + text, + roundTrip, + pth, + ) + if !ok { + return + } + } +} + +func TestContainerDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "container", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + _, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertIsDeserializationErrorAndEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestWrapperRoundTripOK(t *testing.T) { + pths := ourtesting.FindFilesBySuffixRecursively( + filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Expected", + "wrapper", + ), + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + deserialized, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertNoDeserializationError(t, deseriaErr, pth) + if !ok { + return + } + + if _, ok := deserialized.(ourtypes.IWrapper); !ok { + t.Fatalf( + "Expected an instance of IWrapper, "+ + "but got %T: %v", + deserialized, deserialized, + ) + return + } + + buf := &bytes.Buffer{} + encoder := xml.NewEncoder(buf) + encoder.Indent("", "\t") + + seriaErr := ourxmlization.Marshal(encoder, deserialized, true) + ok = assertNoSerializationError(t, seriaErr, pth) + if !ok { + return + } + + roundTrip := string(buf.Bytes()) + + ok = assertSerializationEqualsDeserialization( + t, + text, + roundTrip, + pth, + ) + if !ok { + return + } + } +} + +func TestWrapperDeserializationFail(t *testing.T) { + pattern := filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Unexpected", + "Unserializable", + "*", // This asterisk represents the cause. + "wrapper", + ) + + causeDirs, err := filepath.Glob(pattern) + if err != nil { + panic( + fmt.Sprintf( + "Failed to find cause directories matching %s: %s", + pattern, err.Error(), + ), + ) + } + + for _, causeDir := range causeDirs { + pths := ourtesting.FindFilesBySuffixRecursively( + causeDir, + ".xml", + ) + sort.Strings(pths) + + for _, pth := range pths { + relPth, err := filepath.Rel(ourtesting.TestDataDir, pth) + if err != nil { + panic( + fmt.Sprintf( + "Failed to compute the relative path of %s to %s: %s", + ourtesting.TestDataDir, pth, err.Error(), + ), + ) + } + + expectedPth := filepath.Join( + ourtesting.TestDataDir, + "DeserializationError", + filepath.Dir(relPth), + filepath.Base(relPth)+".error", + ) + + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + return + } + text := string(bb) + + decoder := xml.NewDecoder(strings.NewReader(text)) + + _, deseriaErr := ourxmlization.Unmarshal(decoder) + ok := assertIsDeserializationErrorAndEqualsExpectedOrRecord( + t, deseriaErr, pth, expectedPth, + ) + if !ok { + return + } + } + } +} + +func TestDuplicatePropertyFails(t *testing.T) { + pth := filepath.Join( + ourtesting.TestDataDir, + "Xml", + "Expected", + "someItem", + "minimal.xml", + ) + + bb, err := os.ReadFile(pth) + if err != nil { + t.Fatalf("Failed to read the file %s: %s", pth, err.Error()) + } + + text := string(bb) + + // We cut the element of the property out of the recorded example and put it in + // a second time, just before the closing tag of the instance. + start := strings.Index(text, "") + + var property string + if start >= 0 { + end := strings.Index( + text[start:], "", + ) + start + property = text[start : end+7] + } else { + // The element is written self-closing in the example, an empty list being + // the usual reason. We write that very element out ourselves. + property = "" + } + + insertionIndex := strings.LastIndex( + text, "", + ) + if insertionIndex < 0 { + t.Fatalf( + "We expect the recorded example to contain the closing tag %s, "+ + "but it does not: %s", + "", pth, + ) + } + + brokenText := text[:insertionIndex] + property + text[insertionIndex:] + + decoder := xml.NewDecoder(strings.NewReader(brokenText)) + + _, deseriaErr := ourxmlization.Unmarshal(decoder) + if deseriaErr == nil { + t.Fatalf( + "Expected a de-serialization error when the property %s is given twice, "+ + "but got none", + "name", + ) + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/xmlization.go b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/xmlization.go new file mode 100644 index 000000000..4ec17495d --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/expected_output/xmlization/xmlization.go @@ -0,0 +1,1221 @@ +// Package xmlization de/serializes model instances to and from XML. +// +// To de-serialize, call one of the `Unmarshal*` functions. +// +// To serialize, call the [Marshal] function. +package xmlization + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import ( + "encoding/xml" + "fmt" + ourreporting "github.com/dummy-works/dummy/reporting" + ourtypes "github.com/dummy-works/dummy/types" + xmlcommon "github.com/dummy-works/dummy/internal/xmlcommon" +) + +// Namespace is the XML namespace in which all the elements live. +const Namespace = xmlcommon.Namespace + +// region De-serialization + +// Represent an error during the de-serialization. +// +// Implements `error`. +type DeserializationError = xmlcommon.DeserializationError + +// Report that the required property with the given `name` has not been observed. +func missingProperty(name string) error { + return xmlcommon.NewDeserializationError( + fmt.Sprintf( + "The required property '%s' is missing", + name, + ), + ) +} + +// Report that the property with the given `local` name has been observed more +// than once. +func duplicatePropertyError(local string) error { + return xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Property %s occurred more than once", + local, + ), + ) +} + +// Report that we got a start element with the `local` name, but expected a start +// element with the `expectedLocal` name. +func unexpectedStartElement(local string, expectedLocal string) error { + return xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Expected a start element with local name %s, "+ + "but got a start element with local name %s", + expectedLocal, local, + ), + ) +} + +// Report that the start element with the `local` name does not discriminate any of +// the alternatives of `expectedType`. +func unexpectedDiscriminator(local string, expectedType string) error { + return xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Unexpected start element %s as discriminator for %s", + local, expectedType, + ), + ) +} + +// Report that we got an item delimited by a start element with the `local` name, +// but expected the delimiter with the `expectedLocal` name. +func unexpectedItemElement(local string, expectedLocal string) error { + return xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Expected start element %s as an item delimiter, "+ + "but got %s", + expectedLocal, local, + ), + ) +} + +// Read a list of values as a sequence of XML elements. +// +// Every start element is considered to mark the start of an item serialization. We +// stop the reading as soon as we encounter a non-start element. +// +// That last non-start element is returned as `next` element. +// +// An item is read with [xmlcommon.ReadElementDispatched], so `readItem` decides on its own +// which local names it accepts. A list of instances and a list of scalars therefore +// share this one function: an instance is discriminated by its own element name, +// while a scalar is expected in an element named `v`. +// +// `T` is left unconstrained (instead of `ourtypes.IClass`) since this +// function never invokes any `ourtypes.IClass` method on `T` -- this lets it +// be reused for a list of scalars and for a list of a named union as well, +// the latter being deliberately not an `ourtypes.IClass` itself. +func readListOf[T any]( + decoder *xml.Decoder, + current xml.Token, + readItem func( + aDecoder *xml.Decoder, + aCurrent xml.Token, + aLocal string, + ) (value T, aNext xml.Token, anErr error), +) (values []T, next xml.Token, err error) { + i := 0 + for { + current, err = xmlcommon.SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + + if _, ok := current.(xml.StartElement); !ok { + break + } + + var value T + var valueErr error + value, current, valueErr = xmlcommon.ReadElementDispatched( + decoder, current, readItem, + ) + if valueErr != nil { + if deseriaErr, ok := valueErr.(*DeserializationError); ok { + deseriaErr.Path.PrependIndex( + &ourreporting.IndexSegment{Index: i}, + ) + } + err = valueErr + return + } + + values = append(values, value) + + i++ + } + + next = current + return +} + +// Turn a just-read value into a pointer, so that it can be stored in an optional +// property. +// +// An optional is represented as a pointer, so the value has to live outside the +// caller's frame. This allocates exactly the one value that the caller would +// otherwise allocate by taking the address of its own local variable, and no more. +// +// The arguments are the *results* of a read, not the reader itself. Go passes +// a multi-valued call on as a complete argument list, so this composes with any read, +// no matter how many arguments that read takes on its own -- +// `readOptional(xmlcommon.ReadTextAs_long(decoder, current))` just as much as +// `readOptional(readTuple2(decoder, current, readAtV1_X, readAtV2_Y))`, which no +// reader-taking signature could express, since the item readers of a tuple vary in +// number and in type. +func readOptional[T any]( + value T, + current xml.Token, + err error, +) (*T, xml.Token, error) { + if err != nil { + return nil, current, err + } + + return &value, current, nil +} + +// Advance to the next property of an instance serialized as a sequence of XML +// elements, and return the `local` name of the corresponding start element. +// +// The resulting `next` token points to the content of that element. +// +// If there are no more properties, `ok` is false and `next` points to the token +// which stopped the reading, be it a non-start element or [eof]. +// +// `interfaceName` is only used for error reporting. +func nextProperty( + decoder *xml.Decoder, + current xml.Token, + interfaceName string, +) (local string, next xml.Token, ok bool, err error) { + current, err = xmlcommon.SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + + if _, isEOF := current.(xmlcommon.Eof); isEOF { + next = current + return + } + + startElement, isStartElement := current.(xml.StartElement) + if !isStartElement { + if charData, isCharData := current.(xml.CharData); isCharData { + err = xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Expected a sequence of XML elements representing properties "+ + "of %s, but got text: %s", + interfaceName, string(charData), + ), + ) + return + } + + next = current + return + } + + local, err = xmlcommon.ExtractLocalNameFromStartElement(startElement) + if err != nil { + return + } + + // Move the current to the content of the XML element + next, err = xmlcommon.ReadNext(decoder, current) + if err != nil { + return + } + + ok = true + return +} + +// Conclude the reading of the property delimited by the element with the `local` +// name. +// +// If `valueErr` is set, report it in the context of that property. Otherwise, +// consume the end element, so that the resulting `next` token points to the first +// token just after it. An unexpected content in place of the end element, such as +// a text or an excess element, is reported in the context of the property as well. +func concludeProperty( + decoder *xml.Decoder, + current xml.Token, + local string, + valueErr error, +) (next xml.Token, err error) { + err = valueErr + + if err == nil { + current, err = xmlcommon.SkipEmptyTextWhitespaceAndComments(decoder, current) + } + + if err == nil { + err = xmlcommon.CheckEndElement(current, local) + } + + if err != nil { + if deseriaErr, ok := err.(*DeserializationError); ok { + deseriaErr.Path.PrependName( + &ourreporting.NameSegment{Name: local}, + ) + } + return + } + + next, err = xmlcommon.ReadNext(decoder, current) + return +} + +// Wrap `readByLocal` so that its errors are reported at the element it reads. +// +// We use it where an instance of a class is its own element, at the root or +// nested in a property, so that the path names the element of the instance, +// *e.g.*, `someRoot/property/someClass/someProperty`, as in the other SDKs. +// The items of a list and of a tuple are not wrapped, as their index already +// selects the element of the instance. +func namingElement[T any]( + readByLocal func( + aDecoder *xml.Decoder, + aCurrent xml.Token, + aLocal string, + ) (value T, aNext xml.Token, anErr error), +) func( + decoder *xml.Decoder, + current xml.Token, + local string, +) (value T, next xml.Token, err error) { + return func( + decoder *xml.Decoder, + current xml.Token, + local string, + ) (value T, next xml.Token, err error) { + value, next, err = readByLocal(decoder, current, local) + if err != nil { + if deseriaErr, ok := err.(*DeserializationError); ok { + deseriaErr.Path.PrependName( + &ourreporting.NameSegment{Name: local}, + ) + } + } + return + } +} + +// De-serialize an instance of [ourtypes.IItem] based on the `local` name +// of its start element. +// +// The `current` token is expected to point to the content of that start element, and +// the resulting `next` token points to its end element. +func readItemDispatched( + decoder *xml.Decoder, + current xml.Token, + local string, +) (instance ourtypes.IItem, + next xml.Token, + err error, +) { + switch local { + case "someItem": + instance, next, err = readSomeItemAsSequence(decoder, current) + case "specialItem": + instance, next, err = readSpecialItemAsSequence(decoder, current) + default: + err = unexpectedDiscriminator(local, "IItem") + } + return +} + +// De-serialize the instance of [ourtypes.ISomeItem] +// as a sequence of XML elements, each representing a property +// of [ourtypes.ISomeItem]. +// +// The reading stops as soon as we encounter a non-start element, and we return +// that token as the `next` token. +func readSomeItemAsSequence( + decoder *xml.Decoder, + current xml.Token, +) (instance ourtypes.ISomeItem, + next xml.Token, + err error, +) { + var theName string + var theNickname *string + + foundName := false + foundNickname := false + + for { + var local string + var ok bool + local, current, ok, err = nextProperty(decoder, current, "ISomeItem") + if err != nil { + return + } + if !ok { + break + } + + var valueErr error + switch local { + case "name": + if foundName { + valueErr = duplicatePropertyError(local) + break + } + theName, current, valueErr = xmlcommon.ReadText( + decoder, current, + ) + foundName = true + case "nickname": + if foundNickname { + valueErr = duplicatePropertyError(local) + break + } + theNickname, current, valueErr = readOptional( + xmlcommon.ReadText(decoder, current), + ) + foundNickname = true + default: + valueErr = xmlcommon.NewDeserializationError( + "Unexpected property", + ) + } + + current, err = concludeProperty(decoder, current, local, valueErr) + if err != nil { + return + } + } + + next = current + + if !foundName { + err = missingProperty("name") + return + } + + instance = ourtypes.NewSomeItem( + theName, + ) + instance.SetNickname(theNickname) + return +} + +// De-serialize an instance of [ourtypes.ISomeItem] based on the `local` name +// of its start element. +// +// The `current` token is expected to point to the content of that start element, and +// the resulting `next` token points to its end element. +func readSomeItemDispatched( + decoder *xml.Decoder, + current xml.Token, + local string, +) (instance ourtypes.ISomeItem, + next xml.Token, + err error, +) { + switch local { + case "specialItem": + instance, next, err = readSpecialItemAsSequence(decoder, current) + case "someItem": + instance, next, err = readSomeItemAsSequence(decoder, current) + default: + err = unexpectedDiscriminator(local, "ISomeItem") + } + return +} + +// De-serialize the instance of [ourtypes.ISpecialItem] +// as a sequence of XML elements, each representing a property +// of [ourtypes.ISpecialItem]. +// +// The reading stops as soon as we encounter a non-start element, and we return +// that token as the `next` token. +func readSpecialItemAsSequence( + decoder *xml.Decoder, + current xml.Token, +) (instance ourtypes.ISpecialItem, + next xml.Token, + err error, +) { + var theName string + var theNickname *string + var theLevel int64 + + foundName := false + foundNickname := false + foundLevel := false + + for { + var local string + var ok bool + local, current, ok, err = nextProperty(decoder, current, "ISpecialItem") + if err != nil { + return + } + if !ok { + break + } + + var valueErr error + switch local { + case "name": + if foundName { + valueErr = duplicatePropertyError(local) + break + } + theName, current, valueErr = xmlcommon.ReadText( + decoder, current, + ) + foundName = true + case "nickname": + if foundNickname { + valueErr = duplicatePropertyError(local) + break + } + theNickname, current, valueErr = readOptional( + xmlcommon.ReadText(decoder, current), + ) + foundNickname = true + case "level": + if foundLevel { + valueErr = duplicatePropertyError(local) + break + } + theLevel, current, valueErr = xmlcommon.ReadTextAs_long( + decoder, current, + ) + foundLevel = true + default: + valueErr = xmlcommon.NewDeserializationError( + "Unexpected property", + ) + } + + current, err = concludeProperty(decoder, current, local, valueErr) + if err != nil { + return + } + } + + next = current + + if !foundName { + err = missingProperty("name") + return + } + + if !foundLevel { + err = missingProperty("level") + return + } + + instance = ourtypes.NewSpecialItem( + theName, + theLevel, + ) + instance.SetNickname(theNickname) + return +} + +// De-serialize an instance of [ourtypes.ISpecialItem] based on the `local` name +// of its start element. +// +// The `current` token is expected to point to the content of that start element, and +// the resulting `next` token points to its end element. +func readSpecialItemDispatched( + decoder *xml.Decoder, + current xml.Token, + local string, +) (instance ourtypes.ISpecialItem, + next xml.Token, + err error, +) { + switch local { + case "specialItem": + instance, next, err = readSpecialItemAsSequence(decoder, current) + default: + err = unexpectedStartElement(local, "specialItem") + } + return +} + +// De-serialize the instance of [ourtypes.IContainer] +// as a sequence of XML elements, each representing a property +// of [ourtypes.IContainer]. +// +// The reading stops as soon as we encounter a non-start element, and we return +// that token as the `next` token. +func readContainerAsSequence( + decoder *xml.Decoder, + current xml.Token, +) (instance ourtypes.IContainer, + next xml.Token, + err error, +) { + var theItems []ourtypes.IItem + var theSomeItems []ourtypes.ISomeItem + var theSpecialItems []ourtypes.ISpecialItem + + foundItems := false + foundSomeItems := false + foundSpecialItems := false + + for { + var local string + var ok bool + local, current, ok, err = nextProperty(decoder, current, "IContainer") + if err != nil { + return + } + if !ok { + break + } + + var valueErr error + switch local { + case "items": + if foundItems { + valueErr = duplicatePropertyError(local) + break + } + theItems, current, valueErr = readListOf( + decoder, current, readItemDispatched, + ) + foundItems = true + case "someItems": + if foundSomeItems { + valueErr = duplicatePropertyError(local) + break + } + theSomeItems, current, valueErr = readListOf( + decoder, current, readSomeItemDispatched, + ) + foundSomeItems = true + case "specialItems": + if foundSpecialItems { + valueErr = duplicatePropertyError(local) + break + } + theSpecialItems, current, valueErr = readListOf( + decoder, current, readSpecialItemDispatched, + ) + foundSpecialItems = true + default: + valueErr = xmlcommon.NewDeserializationError( + "Unexpected property", + ) + } + + current, err = concludeProperty(decoder, current, local, valueErr) + if err != nil { + return + } + } + + next = current + + if !foundItems { + err = missingProperty("items") + return + } + + if !foundSomeItems { + err = missingProperty("someItems") + return + } + + instance = ourtypes.NewContainer( + theItems, + theSomeItems, + ) + instance.SetSpecialItems(theSpecialItems) + return +} + +// De-serialize the instance of [ourtypes.IWrapper] +// as a sequence of XML elements, each representing a property +// of [ourtypes.IWrapper]. +// +// The reading stops as soon as we encounter a non-start element, and we return +// that token as the `next` token. +func readWrapperAsSequence( + decoder *xml.Decoder, + current xml.Token, +) (instance ourtypes.IWrapper, + next xml.Token, + err error, +) { + var theContainer ourtypes.IContainer + + foundContainer := false + + for { + var local string + var ok bool + local, current, ok, err = nextProperty(decoder, current, "IWrapper") + if err != nil { + return + } + if !ok { + break + } + + var valueErr error + switch local { + case "container": + if foundContainer { + valueErr = duplicatePropertyError(local) + break + } + theContainer, current, valueErr = readContainerAsSequence( + decoder, current, + ) + foundContainer = true + default: + valueErr = xmlcommon.NewDeserializationError( + "Unexpected property", + ) + } + + current, err = concludeProperty(decoder, current, local, valueErr) + if err != nil { + return + } + } + + next = current + + if !foundContainer { + err = missingProperty("container") + return + } + + instance = ourtypes.NewWrapper( + theContainer, + ) + return +} + +// De-serialize an instance of [ourtypes.IClass] based on the `local` name +// of its start element. +// +// The `current` token is expected to point to the content of that start element, and +// the resulting `next` token points to its end element. +func readClassDispatched( + decoder *xml.Decoder, + current xml.Token, + local string, +) (instance ourtypes.IClass, + next xml.Token, + err error, +) { + switch local { + case "someItem": + instance, next, err = readSomeItemAsSequence(decoder, current) + case "specialItem": + instance, next, err = readSpecialItemAsSequence(decoder, current) + case "container": + instance, next, err = readContainerAsSequence(decoder, current) + case "wrapper": + instance, next, err = readWrapperAsSequence(decoder, current) + default: + err = xmlcommon.NewDeserializationError( + fmt.Sprintf( + "Unexpected XML element name %s as class discriminator", + local, + ), + ) + } + return +} + +// Unmarshal an instance of [ourtypes.IClass] serialized as an XML element. +// +// The XML element must live in the [Namespace] space. +func Unmarshal( + decoder *xml.Decoder, +) (instance ourtypes.IClass, err error) { + var current xml.Token + current, err = xmlcommon.ReadNext(decoder, nil) + if err != nil { + return + } + + // NOTE (mristin): + // We skip the prolog before the root element, *i.e.*, the XML declaration, + // the processing instructions, the comments and the whitespace. + for { + current, err = xmlcommon.SkipEmptyTextWhitespaceAndComments(decoder, current) + if err != nil { + return + } + + if _, ok := current.(xml.ProcInst); !ok { + break + } + + current, err = xmlcommon.ReadNext(decoder, current) + if err != nil { + return + } + } + + instance, _, err = xmlcommon.ReadElementDispatched( + decoder, current, namingElement(readClassDispatched), + ) + return +} + +// endregion + +// region Serialization + +// Represent an error during the serialization. +// +// Implements `error`. +type SerializationError = xmlcommon.SerializationError + +// Write the optional `that` as an XML element with the `local` name, or write +// nothing at all if it is not set. +// +// Do not flush. +// +// A scalar and a tuple are not nilable in Golang, so an optional one is represented +// as a pointer. The pointer is dereferenced here, so that `writeContent` sees only +// the value. See also [writeOptionalInstance] and [writeOptionalSlice], which differ +// from this function only in how the presence is decided. +func writeOptionalPointer[T any]( + encoder *xml.Encoder, + local string, + that *T, + writeContent func(anEncoder *xml.Encoder, aValue T) (anErr error), +) (err error) { + if that == nil { + return + } + + return xmlcommon.WriteElement(encoder, local, *that, writeContent) +} + +// Write the optional instance `that` as an XML element with the `local` name, or +// write nothing at all if it is not set. +// +// Do not flush. +// +// An instance is represented as an interface and a named union as a pointer to +// a struct, both of which are nil on their own, so -- unlike in [writeOptionalPointer] -- +// there is no pointer to dereference. +// +// Golang does not allow a value of a type parameter to be compared against `nil`, +// and `any(that) == nil` would not do either: a nil *pointer* converted to `any` is +// a non-nil `any` which carries the type of that pointer. Comparing against +// the zero value of `T` covers both, as it compares nil against nil for +// an interface, and a nil pointer against a nil pointer of the same type for +// a named union. +func writeOptionalInstance[T any]( + encoder *xml.Encoder, + local string, + that T, + writeContent func(anEncoder *xml.Encoder, aValue T) (anErr error), +) (err error) { + var unset T + if any(that) == any(unset) { + return + } + + return xmlcommon.WriteElement(encoder, local, that, writeContent) +} + +// Write the optional `that` as an XML element with the `local` name, or write +// nothing at all if it is not set. +// +// Do not flush. +// +// A list and the bytes are represented as a slice, which is nil on its own, but -- +// unlike an instance in [writeOptionalInstance] -- can not be compared against +// the zero value, as a slice is not comparable at all. Mind that a nil slice and +// an empty slice differ here: only the former is considered absent, while +// the latter is written as an empty XML element. +func writeOptionalSlice[T any]( + encoder *xml.Encoder, + local string, + that []T, + writeContent func(anEncoder *xml.Encoder, aValue []T) (anErr error), +) (err error) { + if that == nil { + return + } + + return xmlcommon.WriteElement(encoder, local, that, writeContent) +} + +// Write the items of the `list`, each as an XML element of its own. +// +// Do not flush. +// +// The element *around* the list is framed by whoever writes the list, see +// the generated `writeListOf*` functions. Every item frames its own element +// through `writeItem`: an instance is written as an element named after its model +// type, while a scalar is wrapped in the element `v`. A list of instances and +// a list of scalars therefore share this one function. +func writeList[T any]( + encoder *xml.Encoder, + list []T, + writeItem func(anEncoder *xml.Encoder, aValue T) (anErr error), +) (err error) { + for i, item := range list { + err = writeItem(encoder, item) + if err != nil { + if seriaErr, ok := err.(*SerializationError); ok { + seriaErr.Path.PrependIndex( + &ourreporting.IndexSegment{Index: i}, + ) + } + return + } + } + + return +} + +// Conclude the writing of the property read by `getter` by attributing the error, +// if any, to that property. +// +// Do not flush. +// +// `getter` is the getter of the property *as it is spelled in Golang*, `Value()` +// and not `value`, since it is prepended to the path of a serialization error, +// which [SerializationError.PathString] renders as a Golang expression through +// [ourreporting.ToGolangPath]. (Golang has no way to name a member at compile time, +// so the getter has to be spelled out; mind that the de-serialization reports +// an XPath instead, and hence prepends the XML name there.) +// +// The write itself is given as its *result*, not as a function to be called, so that +// this one function concludes every property, no matter which of the `write*` +// functions wrote it, and no matter how many arguments that function took. Golang +// evaluates the argument, hence performs the write, before this call. +func finishProperty( + getter string, + err error, +) error { + if err != nil { + if seriaErr, ok := err.(*SerializationError); ok { + seriaErr.Path.PrependName( + &ourreporting.NameSegment{Name: getter}, + ) + } + return err + } + + return nil +} + +// Write the instance `that` as an XML element named after its model type. +// +// Do not flush. +// +// This is the content writer of every instance which is not embedded in the element +// of its property, be it a property, a list item or a tuple item: [Marshal] picks +// the element name from the runtime model type. +// +// Golang function values are invariant in their parameter type, so [Marshal], which +// takes the wide [ourtypes.IClass], can not be used where a writer of a more +// specific interface is expected -- this generic function exists solely to narrow +// the parameter type to `T`. Golang can not infer `T` from the context here, so +// every call site instantiates it explicitly, *e.g.*, +// `writeInstance[ourtypes.IReference]`, and passes it on as that instantiated +// function value, without a closure. +func writeInstance[T ourtypes.IClass]( + encoder *xml.Encoder, + that T, +) error { + return writeClass(encoder, that, false) +} + +// Write `that` as an XML element with the `local` name representing its model type. +// +// Do not flush. +// +// If `withNamespace` is set, the `xmlns` attribute is set in the outer XML element. +// +// Unlike [xmlcommon.WriteElement], which frames a *property*, this function frames +// an instance in the element which discriminates its model type, and is therefore +// the one place where the XML namespace can be set. +func writeClassElement[T any]( + encoder *xml.Encoder, + local string, + withNamespace bool, + that T, + writeTAsSequence func(anEncoder *xml.Encoder, aValue T) (anErr error), +) (err error) { + err = xmlcommon.WriteStartElement(encoder, local, withNamespace) + if err != nil { + return + } + + err = writeTAsSequence(encoder, that) + if err != nil { + return + } + + err = xmlcommon.WriteEndElement(encoder, local, withNamespace) + return +} + +// Write the items of the `list` as a sequence of XML elements. +// +// Do not flush. +func writeListOf_IItem( + encoder *xml.Encoder, + list []ourtypes.IItem, +) error { + return writeList( + encoder, list, writeInstance[ourtypes.IItem], + ) +} + +// Write the items of the `list` as a sequence of XML elements. +// +// Do not flush. +func writeListOf_ISomeItem( + encoder *xml.Encoder, + list []ourtypes.ISomeItem, +) error { + return writeList( + encoder, list, writeInstance[ourtypes.ISomeItem], + ) +} + +// Write the items of the `list` as a sequence of XML elements. +// +// Do not flush. +func writeListOf_ISpecialItem( + encoder *xml.Encoder, + list []ourtypes.ISpecialItem, +) error { + return writeList( + encoder, list, writeInstance[ourtypes.ISpecialItem], + ) +} + +// Serialize the instance +// of [ourtypes.ISomeItem] +// as a sequence of properties, each represented as an XML element. +// +// The XML namespace is expected to be set in the one of the parent elements +// enclosing the sequence. +// +// Do not flush. +func writeSomeItemAsSequence( + encoder *xml.Encoder, + that ourtypes.ISomeItem, +) (err error) { + err = finishProperty( + "Name()", + xmlcommon.WriteElement( + encoder, "name", that.Name(), xmlcommon.WriteAsText_string, + ), + ) + if err != nil { + return + } + + err = finishProperty( + "Nickname()", + writeOptionalPointer( + encoder, "nickname", that.Nickname(), xmlcommon.WriteAsText_string, + ), + ) + if err != nil { + return + } + + return +} + +// Serialize the instance +// of [ourtypes.ISpecialItem] +// as a sequence of properties, each represented as an XML element. +// +// The XML namespace is expected to be set in the one of the parent elements +// enclosing the sequence. +// +// Do not flush. +func writeSpecialItemAsSequence( + encoder *xml.Encoder, + that ourtypes.ISpecialItem, +) (err error) { + err = finishProperty( + "Name()", + xmlcommon.WriteElement( + encoder, "name", that.Name(), xmlcommon.WriteAsText_string, + ), + ) + if err != nil { + return + } + + err = finishProperty( + "Nickname()", + writeOptionalPointer( + encoder, "nickname", that.Nickname(), xmlcommon.WriteAsText_string, + ), + ) + if err != nil { + return + } + + err = finishProperty( + "Level()", + xmlcommon.WriteElement( + encoder, "level", that.Level(), xmlcommon.WriteAsText_long, + ), + ) + if err != nil { + return + } + + return +} + +// Serialize the instance +// of [ourtypes.IContainer] +// as a sequence of properties, each represented as an XML element. +// +// The XML namespace is expected to be set in the one of the parent elements +// enclosing the sequence. +// +// Do not flush. +func writeContainerAsSequence( + encoder *xml.Encoder, + that ourtypes.IContainer, +) (err error) { + err = finishProperty( + "Items()", + xmlcommon.WriteElement( + encoder, "items", that.Items(), writeListOf_IItem, + ), + ) + if err != nil { + return + } + + err = finishProperty( + "SomeItems()", + xmlcommon.WriteElement( + encoder, "someItems", that.SomeItems(), writeListOf_ISomeItem, + ), + ) + if err != nil { + return + } + + err = finishProperty( + "SpecialItems()", + writeOptionalSlice( + encoder, "specialItems", that.SpecialItems(), writeListOf_ISpecialItem, + ), + ) + if err != nil { + return + } + + return +} + +// Serialize the instance +// of [ourtypes.IWrapper] +// as a sequence of properties, each represented as an XML element. +// +// The XML namespace is expected to be set in the one of the parent elements +// enclosing the sequence. +// +// Do not flush. +func writeWrapperAsSequence( + encoder *xml.Encoder, + that ourtypes.IWrapper, +) (err error) { + err = finishProperty( + "Container()", + xmlcommon.WriteElement( + encoder, "container", that.Container(), writeContainerAsSequence, + ), + ) + if err != nil { + return + } + + return +} + +// Serialize `that` instance as an XML element named after its model type. +// +// Do not flush. +// +// If `withNamespace` is set, the `xmlns` attribute is set in the XML element +// to [Namespace]. +func writeClass( + encoder *xml.Encoder, + that ourtypes.IClass, + withNamespace bool, +) (err error) { + switch that.ModelType() { + case ourtypes.ModelTypeSomeItem: + err = writeClassElement( + encoder, + "someItem", + withNamespace, + that.(ourtypes.ISomeItem), + writeSomeItemAsSequence, + ) + case ourtypes.ModelTypeSpecialItem: + err = writeClassElement( + encoder, + "specialItem", + withNamespace, + that.(ourtypes.ISpecialItem), + writeSpecialItemAsSequence, + ) + case ourtypes.ModelTypeContainer: + err = writeClassElement( + encoder, + "container", + withNamespace, + that.(ourtypes.IContainer), + writeContainerAsSequence, + ) + case ourtypes.ModelTypeWrapper: + err = writeClassElement( + encoder, + "wrapper", + withNamespace, + that.(ourtypes.IWrapper), + writeWrapperAsSequence, + ) + default: + err = xmlcommon.NewSerializationError( + fmt.Sprintf( + "Unexpected model type: %v", + that.ModelType(), + ), + ) + } + return +} + +// Serialize `that` instance as an XML element, and flush the encoder. +// +// If `withNamespace` is set, the `xmlns` attribute is set in the XML element +// to [Namespace]. +func Marshal( + encoder *xml.Encoder, + that ourtypes.IClass, + withNamespace bool, +) (err error) { + err = writeClass(encoder, that, withNamespace) + if err != nil { + return + } + + return encoder.Flush() +} + +// endregion + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/golang/expected/variance_of_containers/input/snippets/repo_url.txt b/dev/test_data/main/golang/expected/variance_of_containers/input/snippets/repo_url.txt new file mode 100644 index 000000000..44f004031 --- /dev/null +++ b/dev/test_data/main/golang/expected/variance_of_containers/input/snippets/repo_url.txt @@ -0,0 +1 @@ +github.com/dummy-works/dummy \ No newline at end of file diff --git a/dev/test_data/main/golang/expected/visibility/expected_output/common/common.go b/dev/test_data/main/golang/expected/visibility/expected_output/common/common.go index 0f9a80b11..df990e9ed 100644 --- a/dev/test_data/main/golang/expected/visibility/expected_output/common/common.go +++ b/dev/test_data/main/golang/expected/visibility/expected_output/common/common.go @@ -39,6 +39,22 @@ func SliceContains[T any](s []T, v T) bool { return false } +// Check if the slice contains the given instance of a class. +// +// In contrast to [SliceContains], the instance can be of a different type than +// the items of the slice. This is necessary for the items of the generic sequences, +// which are of a type parameter, and for the instances of the descendants of +// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their +// references. +func SliceContainsInstance[T any](s []T, v any) bool { + for _, item := range s { + if any(item) == v { + return true + } + } + return false +} + // Check if any of the elements satisfy the condition. func Some[V any](condition func(V) bool, l []V) bool { ok := false diff --git a/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java index 55c6b8a05..ca62df26a 100644 --- a/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/annotated_assignments/expected_output/src/main/java/dummy/verification/Verification.java @@ -33,9 +33,11 @@ public class Verification { /** * Check an optional class declared with {@code None} and assigned in a loop. + * + *

The argument {@code parents} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IParent} can be passed without a copy. */ public static Boolean firstChildAIsSmall( - List parents) { + List parents) { Optional found = Optional.empty(); for (var parent : parents) { if (parent instanceof IChildA) { @@ -80,10 +82,12 @@ public static Boolean textIsAllowedUnlessLong( /** * Check a non-optional variable declared wider than its value. + * + *

The argument {@code parents} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IParent} can be passed without a copy. */ public static Boolean lastChildBIsNotZero( IParent parent, - List parents) { + List parents) { IParent last = parent; for (var other : parents) { last = other; @@ -316,9 +320,9 @@ public static Boolean finalLocalsAreConsistent( Optional optionalText, IParent parent) { final long limit = 1000; - final String label = text; - final Optional maybeText = optionalText; - final List allTexts = texts; + final var label = text; + final var maybeText = optionalText; + final var allTexts = texts; Set seen = new HashSet(); seen.add(text); final Set reservedSeen = ( @@ -326,7 +330,7 @@ public static Boolean finalLocalsAreConsistent( final Tuple2 pair = new Tuple2<>( text, number); - final IParent base = parent; + final var base = parent; if ( reservedSeen.size() > 0 || StringHelpers.len(label) > limit diff --git a/dev/test_data/main/java/expected/if_statements/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/if_statements/expected_output/src/main/java/dummy/verification/Verification.java index c5eaea096..6c63c6044 100644 --- a/dev/test_data/main/java/expected/if_statements/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/if_statements/expected_output/src/main/java/dummy/verification/Verification.java @@ -237,9 +237,11 @@ public static Boolean narrowingAfterTheOnlyCompletingBranch( /** * Check the narrowing after the {@code continue} and the early return in a loop. + * + *

The argument {@code parents} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IParent} can be passed without a copy. */ public static Boolean childAsHaveTexts( - List parents) { + List parents) { for (var parent : parents) { if (!(parent instanceof IChildA)) { continue; @@ -256,9 +258,11 @@ public static Boolean childAsHaveTexts( /** * Check the narrowing after the {@code continue} in a loop with a {@code break}. + * + *

The argument {@code parents} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IParent} can be passed without a copy. */ public static Boolean textsBeforeContainerAreShort( - List parents) { + List parents) { long total = 0; for (var parent : parents) { if (parent instanceof IContainer) { @@ -287,10 +291,12 @@ public static Boolean textOrDefaultIsShort( /** * Check the narrowing of a variable to a class by the assigned value. + * + *

The argument {@code parents} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IParent} can be passed without a copy. */ public static Boolean lastChildAIsSmall( IParent parent, - List parents) { + List parents) { var last = parent; for (var other : parents) { if (other instanceof IChildA) { diff --git a/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java index 289665344..a7197d9a1 100644 --- a/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java +++ b/dev/test_data/main/java/expected/list_of_classes/expected_output/src/main/java/dummy/verification/Verification.java @@ -61,9 +61,11 @@ public static List simplesWith( * *

The appended items are narrowed down to {@link dummy.types.impl.SomeItem}, while the list * holds {@link dummy.types.model.IAbstractItem}'s. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IAbstractItem} can be passed without a copy. */ public static List someItemsOf( - List items) { + List items) { List result = new java.util.ArrayList(); for (var item : items) { if (item instanceof ISomeItem) { diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/StringHelpers.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/StringHelpers.java new file mode 100644 index 000000000..6fb64774d --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/StringHelpers.java @@ -0,0 +1,142 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Provide string operations which follow the Python implementation, since + * Python is the language of the meta-model specifications. + * + *

The lengths and the positions count the characters (code points), and not + * the UTF-16 code units of the Java strings. Hence, a character beyond the Basic + * Multilingual Plane counts as one, though it takes two UTF-16 code units + * (a surrogate pair). + */ +public final class StringHelpers { + private StringHelpers() { + // Prevent instantiation + } + + /** + * Resolve {@code position} in a string of {@code length} as Python does + * in slicing. + * + *

A negative position counts from the end, and the positions out of range + * are clamped to the string. + * + * @param position to be resolved + * @param length of the string + * @return the resolved position within {@code [0, length]} + */ + private static int resolvePosition(long position, int length) { + if (position < 0) { + return (int) Math.max(position + length, 0); + } + + return (int) Math.min(position, length); + } + + /** + * Count the characters (code points) of {@code text}. + * + *

We follow the Python implementation of {@code len}, since Python is + * the language of the meta-model specifications. Hence, a character beyond + * the Basic Multilingual Plane counts as one, unlike in {@code text.length()}. + * + * @param text to be measured + * @return the number of characters + */ + public static int len(String text) { + return text.codePointCount(0, text.length()); + } + + /** + * Slice {@code text} from {@code start} up to {@code end}, exclusive. + * + *

We follow the Python implementation of slicing, since Python is + * the language of the meta-model specifications. Hence, the positions count + * the characters (code points), a negative position counts from the end, + * the positions out of range are clamped to the string, and the slice is empty + * if {@code start} is not before {@code end}. + * + * @param text to be sliced + * @param start of the slice, inclusive + * @param end of the slice, exclusive + * @return the slice + */ + public static String slice(String text, long start, long end) { + final int length = len(text); + final int theStart = resolvePosition(start, length); + final int theEnd = resolvePosition(end, length); + + if (theStart >= theEnd) { + return ""; + } + + final int startOffset = text.offsetByCodePoints(0, theStart); + final int endOffset = text.offsetByCodePoints(startOffset, theEnd - theStart); + return text.substring(startOffset, endOffset); + } + + /** + * Slice {@code text} from {@code start} up to its end. + * + *

See {@link #slice(String, long, long)} for the semantics. + * + * @param text to be sliced + * @param start of the slice, inclusive + * @return the slice + */ + public static String slice(String text, long start) { + return slice(text, start, Long.MAX_VALUE); + } + + /** + * Find the first {@code sub} in {@code text} from {@code start} on. + * + *

We follow the Python implementation of {@code str.find}, since Python + * is the language of the meta-model specifications. Hence, the positions count + * the characters (code points), a negative {@code start} counts from the end, + * and a {@code start} beyond the end of {@code text} gives -1. + * + * @param text to be searched in + * @param sub to be searched for + * @param start of the search + * @return the position of {@code sub} in {@code text}, or -1 if not found + */ + public static long find(String text, String sub, long start) { + final int length = len(text); + final long theStart = start < 0 ? Math.max(start + length, 0) : start; + if (theStart > length) { + return -1; + } + + final int startOffset = text.offsetByCodePoints(0, (int) theStart); + final int offset = text.indexOf(sub, startOffset); + if (offset == -1) { + return -1; + } + + return theStart + text.codePointCount(startOffset, offset); + } + + /** + * Find the first {@code sub} in {@code text}. + * + *

See {@link #find(String, String, long)} for the semantics. + * + * @param text to be searched in + * @param sub to be searched for + * @return the position of {@code sub} in {@code text}, or -1 if not found + */ + public static long find(String text, String sub) { + return find(text, sub, 0); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple1.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple1.java new file mode 100644 index 000000000..baa4b7a97 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple1.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 1 item(s). + */ +public record Tuple1(T1 item1) { + /** + * Count the items of the tuple. + */ + public int size() { + return 1; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple2.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple2.java new file mode 100644 index 000000000..ee3123a29 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple2.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 2 item(s). + */ +public record Tuple2(T1 item1, T2 item2) { + /** + * Count the items of the tuple. + */ + public int size() { + return 2; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple3.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple3.java new file mode 100644 index 000000000..fac1f9e6d --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple3.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 3 item(s). + */ +public record Tuple3(T1 item1, T2 item2, T3 item3) { + /** + * Count the items of the tuple. + */ + public int size() { + return 3; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple4.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple4.java new file mode 100644 index 000000000..47e2fd515 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple4.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 4 item(s). + */ +public record Tuple4(T1 item1, T2 item2, T3 item3, T4 item4) { + /** + * Count the items of the tuple. + */ + public int size() { + return 4; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple5.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple5.java new file mode 100644 index 000000000..94bf6d723 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple5.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 5 item(s). + */ +public record Tuple5(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) { + /** + * Count the items of the tuple. + */ + public int size() { + return 5; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple6.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple6.java new file mode 100644 index 000000000..663ee4bcc --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple6.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 6 item(s). + */ +public record Tuple6(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) { + /** + * Count the items of the tuple. + */ + public int size() { + return 6; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple7.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple7.java new file mode 100644 index 000000000..8c5cbe846 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple7.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 7 item(s). + */ +public record Tuple7(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) { + /** + * Count the items of the tuple. + */ + public int size() { + return 7; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple8.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple8.java new file mode 100644 index 000000000..3ba3c0908 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/common/Tuple8.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.common; + +/** + * Represent a fixed-size heterogeneous tuple of 8 item(s). + */ +public record Tuple8(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) { + /** + * Count the items of the tuple. + */ + public int size() { + return 8; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/constants/Constants.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/constants/Constants.java new file mode 100644 index 000000000..bf920884e --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/constants/Constants.java @@ -0,0 +1,37 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.constants; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collector; +import java.util.stream.Stream; + + + +// Helper to generate read-only collections with less boilerplate. +// See: https://stackoverflow.com/a/37406054 +class _ImmutableCollector { + public static Collector, Set> toImmutableSet() { + return Collector.of(HashSet::new, Set::add, (l, r) -> { + l.addAll(r); + return l; + }, Collections::unmodifiableSet); + } +} + +/** + * Provide constant values of the meta-model. + */ +public class Constants { + +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/copying/Copying.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/copying/Copying.java new file mode 100644 index 000000000..41b2c4e59 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/copying/Copying.java @@ -0,0 +1,175 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.copying; + +import java.util.List; +import java.util.ArrayList; +import dummy.common.*; +import dummy.types.model.IClass; +import dummy.visitation.AbstractTransformer; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; + +/** + * Allow for making shallow and deep copies of model instances. + */ +public class Copying +{ + private static final _ShallowCopier shallowCopierInstance = new _ShallowCopier(); + + private static final _DeepCopier deepCopierInstance = new _DeepCopier(); + + /** + * Make a shallow copy of {@code that}. + * + *

All the properties are copied by reference. This includes also the lists. + * Hence, a list property is copied by reference, and not, as sometimes might be + * expected, as a new list of underlying references. + * + * @param that to be copied in a shallow manner + */ + @SuppressWarnings("unchecked") + public static T shallow(T that) { + return (T) shallowCopierInstance.transform(that); + } + + /** + * Make a recursively a deep copy of {@code that}. + * + * @param that to be deeply copied in a recursive manner + */ + @SuppressWarnings("unchecked") + public static T deep(T that) { + return (T) deepCopierInstance.transform(that); + } + + /** + * Make a deep copy of {@code that}, copying its items recursively. + */ + private static List deepListOf_IItem( + List that) { + List result = new ArrayList<>(that.size()); + for (IItem item : that) { + result.add(deep(item)); + } + return result; + } + + /** + * Make a deep copy of {@code that}, copying its items recursively. + */ + private static List deepListOf_ISomeItem( + List that) { + List result = new ArrayList<>(that.size()); + for (ISomeItem item : that) { + result.add(deep(item)); + } + return result; + } + + /** + * Make a deep copy of {@code that}, copying its items recursively. + */ + private static List deepListOf_ISpecialItem( + List that) { + List result = new ArrayList<>(that.size()); + for (ISpecialItem item : that) { + result.add(deep(item)); + } + return result; + } + + /** + * Dispatch the making of shallow copies. + */ + private static class _ShallowCopier extends AbstractTransformer { + @Override + public IClass transformSomeItem( + ISomeItem that + ) { + return new SomeItem(that.getName(), that.getNickname().orElse(null)); + } + + @Override + public IClass transformSpecialItem( + ISpecialItem that + ) { + return new SpecialItem( + that.getName(), + that.getLevel(), + that.getNickname().orElse(null)); + } + + @Override + public IClass transformContainer( + IContainer that + ) { + return new Container( + that.getItems(), + that.getSomeItems(), + that.getSpecialItems().orElse(null)); + } + + @Override + public IClass transformWrapper( + IWrapper that + ) { + return new Wrapper(that.getContainer()); + } + } + + /** Dispatch the making of deep copies. */ + private static class _DeepCopier extends AbstractTransformer { + @Override + public IClass transformSomeItem ( + ISomeItem that + ) { + return new SomeItem( + that.getName(), + that.getNickname().orElse(null) + ); + } + + @Override + public IClass transformSpecialItem ( + ISpecialItem that + ) { + return new SpecialItem( + that.getName(), + that.getLevel(), + that.getNickname().orElse(null) + ); + } + + @Override + public IClass transformContainer ( + IContainer that + ) { + return new Container( + deepListOf_IItem(that.getItems()), + deepListOf_ISomeItem(that.getSomeItems()), + that.getSpecialItems().isPresent() + ? deepListOf_ISpecialItem(that.getSpecialItems().get()) + : null + ); + } + + @Override + public IClass transformWrapper ( + IWrapper that + ) { + return new Wrapper( + deep(that.getContainer()) + ); + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhanced.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhanced.java new file mode 100644 index 000000000..d579663c2 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhanced.java @@ -0,0 +1,23 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +public abstract class Enhanced { + protected final EnhancementT enhancement; + + protected Enhanced(EnhancementT enhancement) { + this.enhancement = enhancement; + } + + EnhancementT getEnhancement() { + return enhancement; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedContainer.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedContainer.java new file mode 100644 index 000000000..25f761ac9 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedContainer.java @@ -0,0 +1,110 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.lang.Iterable; +import java.util.Optional; +import java.util.List; +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; + +public class EnhancedContainer + extends Enhanced + implements IContainer { + private final IContainer instance; + + public EnhancedContainer( + IContainer instance, + EnhancementT enhancement + ) { + super(enhancement); + this.instance = instance; + } + + @Override + public List getItems() { + return instance.getItems(); + } + + @Override + public void setItems(List items) { + instance.setItems(items); + } + + @Override + public List getSomeItems() { + return instance.getSomeItems(); + } + + @Override + public void setSomeItems(List someItems) { + instance.setSomeItems(someItems); + } + + @Override + public Optional> getSpecialItems() { + return instance.getSpecialItems(); + } + + @Override + public void setSpecialItems(List specialItems) { + instance.setSpecialItems(specialItems); + } + + public Iterable overSpecialItemsOrEmpty() { + return instance.overSpecialItemsOrEmpty(); + } + + public Long countItems( + List items + ) + { + return instance.countItems( + items + ); + } + + public Iterable descendOnce() { + return instance.descendOnce(); + } + + public Iterable descend() { + return instance.descend(); + } + + public void accept(IVisitor visitor) { + visitor.visitContainer(instance); + } + + public void accept( + IVisitorWithContext visitor, + ContextT context + ) { + visitor.visitContainer(instance, context); + } + + public T transform(ITransformer transformer) { + return transformer.transformContainer(instance); + } + + public T transform( + ITransformerWithContext transformer, + ContextT context + ) { + return transformer.transformContainer(instance, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSomeItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSomeItem.java new file mode 100644 index 000000000..171f027b1 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSomeItem.java @@ -0,0 +1,87 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.lang.Iterable; +import java.util.Optional; +import java.util.List; +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; + +public class EnhancedSomeItem + extends Enhanced + implements ISomeItem { + private final ISomeItem instance; + + public EnhancedSomeItem( + ISomeItem instance, + EnhancementT enhancement + ) { + super(enhancement); + this.instance = instance; + } + + @Override + public String getName() { + return instance.getName(); + } + + @Override + public void setName(String name) { + instance.setName(name); + } + + @Override + public Optional getNickname() { + return instance.getNickname(); + } + + @Override + public void setNickname(String nickname) { + instance.setNickname(nickname); + } + + public Iterable descendOnce() { + return instance.descendOnce(); + } + + public Iterable descend() { + return instance.descend(); + } + + public void accept(IVisitor visitor) { + visitor.visitSomeItem(instance); + } + + public void accept( + IVisitorWithContext visitor, + ContextT context + ) { + visitor.visitSomeItem(instance, context); + } + + public T transform(ITransformer transformer) { + return transformer.transformSomeItem(instance); + } + + public T transform( + ITransformerWithContext transformer, + ContextT context + ) { + return transformer.transformSomeItem(instance, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSpecialItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSpecialItem.java new file mode 100644 index 000000000..e5a5a3861 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedSpecialItem.java @@ -0,0 +1,97 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.lang.Iterable; +import java.util.Optional; +import java.util.List; +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; + +public class EnhancedSpecialItem + extends Enhanced + implements ISpecialItem { + private final ISpecialItem instance; + + public EnhancedSpecialItem( + ISpecialItem instance, + EnhancementT enhancement + ) { + super(enhancement); + this.instance = instance; + } + + @Override + public String getName() { + return instance.getName(); + } + + @Override + public void setName(String name) { + instance.setName(name); + } + + @Override + public Optional getNickname() { + return instance.getNickname(); + } + + @Override + public void setNickname(String nickname) { + instance.setNickname(nickname); + } + + @Override + public Long getLevel() { + return instance.getLevel(); + } + + @Override + public void setLevel(Long level) { + instance.setLevel(level); + } + + public Iterable descendOnce() { + return instance.descendOnce(); + } + + public Iterable descend() { + return instance.descend(); + } + + public void accept(IVisitor visitor) { + visitor.visitSpecialItem(instance); + } + + public void accept( + IVisitorWithContext visitor, + ContextT context + ) { + visitor.visitSpecialItem(instance, context); + } + + public T transform(ITransformer transformer) { + return transformer.transformSpecialItem(instance); + } + + public T transform( + ITransformerWithContext transformer, + ContextT context + ) { + return transformer.transformSpecialItem(instance, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedWrapper.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedWrapper.java new file mode 100644 index 000000000..f1a412dc9 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/EnhancedWrapper.java @@ -0,0 +1,77 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.lang.Iterable; +import java.util.Optional; +import java.util.List; +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; + +public class EnhancedWrapper + extends Enhanced + implements IWrapper { + private final IWrapper instance; + + public EnhancedWrapper( + IWrapper instance, + EnhancementT enhancement + ) { + super(enhancement); + this.instance = instance; + } + + @Override + public IContainer getContainer() { + return instance.getContainer(); + } + + @Override + public void setContainer(IContainer container) { + instance.setContainer(container); + } + + public Iterable descendOnce() { + return instance.descendOnce(); + } + + public Iterable descend() { + return instance.descend(); + } + + public void accept(IVisitor visitor) { + visitor.visitWrapper(instance); + } + + public void accept( + IVisitorWithContext visitor, + ContextT context + ) { + visitor.visitWrapper(instance, context); + } + + public T transform(ITransformer transformer) { + return transformer.transformWrapper(instance); + } + + public T transform( + ITransformerWithContext transformer, + ContextT context + ) { + return transformer.transformWrapper(instance, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhancer.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhancer.java new file mode 100644 index 000000000..3962224db --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Enhancer.java @@ -0,0 +1,64 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.util.function.Function; +import java.util.Optional; +import dummy.enhancing.Unwrapper; +import dummy.types.model.*; + +/** + * Wrap and unwrap the instances of model classes with enhancement. + * + * @param structure of the enhancement + */ +public class Enhancer extends Unwrapper { + private final _Wrapper wrapper; + + /** + * @param enhancementFactory how to enhance the instances. + * + *

If it returns {@code null}, the instance will not be wrapped. However, + * the wrapping will continue recursively. + */ + public Enhancer( + Function> enhancementFactory + ) { + this.wrapper = new _Wrapper<>(enhancementFactory); + } + + /** + * Wrap the instance with an enhancement. + * + *

Double wraps are not allowed to prevent runtime leakage. + * + *

If you use references to the instance objects, you have to update them + * after the wrapping, as the wrapping is recursive. + * + * @param that model instance to be wrapped + * @return {@code that} instance wrapped recursively with enhancements + */ + public IClass wrap( + IClass that + ) { + IClass wrapped; + try { + wrapped = wrapper.transform(that); + } catch (IllegalArgumentException exception) { + throw new UnsupportedOperationException( + "Expected the wrapped instance to be an instance of IClass, " + + "but got: " + that + ); + } + + return wrapped; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Unwrapper.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Unwrapper.java new file mode 100644 index 000000000..588322dc8 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/Unwrapper.java @@ -0,0 +1,56 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.util.Optional; +import dummy.types.model.*; + +/** + * Unwrap enhancements from the wrapped instances. + * + * @param structure of the expected enhancement + */ +public class Unwrapper { + /** + * Unwrap the given model instance. + * + * @param that model instance to be unwrapped + * @return Enhancement, or {@link java.util.Optional#empty()} if {@code that} + * has not been wrapped yet. + */ + public Optional unwrap(IClass that) + { + if (that instanceof Enhanced) { + @SuppressWarnings("unchecked") + Enhanced enhanced = (Enhanced) that; + return Optional.of(enhanced.getEnhancement()); + } else { + return Optional.empty(); + } + } + + /** + * Unwrap the given model instance. + * + * @param that model instance to be unwrapped + * @return Enhancement wrapped around {@code that} + */ + public EnhancementT mustUnwrap(IClass that) + { + Optional value = unwrap(that); + if (!value.isPresent()) { + throw new IllegalArgumentException( + "Expected the instance to have been wrapped, but it was not: " + that + ); + } + return value.get(); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/_Wrapper.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/_Wrapper.java new file mode 100644 index 000000000..f3441fe87 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/enhancing/_Wrapper.java @@ -0,0 +1,162 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.enhancing; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.model.*; +import dummy.visitation.AbstractTransformer; + +class _Wrapper extends AbstractTransformer { + private final Function> enhancementFactory; + + _Wrapper( + Function> enhancementFactory + ) { + this.enhancementFactory = enhancementFactory; + } + + @Override + public IClass transformSomeItem( + ISomeItem that + ) { + if (that instanceof Enhanced) + { + throw new IllegalArgumentException( + "The instance has been already enhanced: " + that + ); + } + + Optional enhancement = enhancementFactory.apply(that); + return !enhancement.isPresent() + ? that + : new EnhancedSomeItem<>( + that, + enhancement.get() + ); + } + + @Override + public IClass transformSpecialItem( + ISpecialItem that + ) { + if (that instanceof Enhanced) + { + throw new IllegalArgumentException( + "The instance has been already enhanced: " + that + ); + } + + Optional enhancement = enhancementFactory.apply(that); + return !enhancement.isPresent() + ? that + : new EnhancedSpecialItem<>( + that, + enhancement.get() + ); + } + + @Override + public IClass transformContainer( + IContainer that + ) { + if (that instanceof Enhanced) + { + throw new IllegalArgumentException( + "The instance has been already enhanced: " + that + ); + } + + that.setItems(wrapListOf_IItem(that.getItems())); + + that.setSomeItems(wrapListOf_ISomeItem(that.getSomeItems())); + + if (that.getSpecialItems().isPresent()) { + that.setSpecialItems( + wrapListOf_ISpecialItem(that.getSpecialItems().get())); + } + + Optional enhancement = enhancementFactory.apply(that); + return !enhancement.isPresent() + ? that + : new EnhancedContainer<>( + that, + enhancement.get() + ); + } + + @Override + public IClass transformWrapper( + IWrapper that + ) { + if (that instanceof Enhanced) + { + throw new IllegalArgumentException( + "The instance has been already enhanced: " + that + ); + } + + that.setContainer(wrap(that.getContainer())); + + Optional enhancement = enhancementFactory.apply(that); + return !enhancement.isPresent() + ? that + : new EnhancedWrapper<>( + that, + enhancement.get() + ); + } + + @SuppressWarnings("unchecked") + private T wrap(T that) { + return (T) transform(that); + } + + /** + * Wrap the instances held by {@code that} recursively in a new container. + */ + private List wrapListOf_IItem( + List that) { + List result = new ArrayList<>(that.size()); + for (IItem item : that) { + result.add(wrap(item)); + } + return result; + } + + /** + * Wrap the instances held by {@code that} recursively in a new container. + */ + private List wrapListOf_ISomeItem( + List that) { + List result = new ArrayList<>(that.size()); + for (ISomeItem item : that) { + result.add(wrap(item)); + } + return result; + } + + /** + * Wrap the instances held by {@code that} recursively in a new container. + */ + private List wrapListOf_ISpecialItem( + List that) { + List result = new ArrayList<>(that.size()); + for (ISpecialItem item : that) { + result.add(wrap(item)); + } + return result; + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/ContainerBuilder.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/ContainerBuilder.java new file mode 100644 index 000000000..8fdad0d8c --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/ContainerBuilder.java @@ -0,0 +1,43 @@ +package dummy.generation; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Builder for the Container type. + */ +public class ContainerBuilder { + private List items; + + private List someItems; + + private List specialItems; + + public ContainerBuilder( + List items, + List someItems) { + this.items = Objects.requireNonNull( + items, + "Argument \"items\" must be non-null."); + this.someItems = Objects.requireNonNull( + someItems, + "Argument \"someItems\" must be non-null."); + } + + public ContainerBuilder setSpecialItems(List specialItems) { + this.specialItems = specialItems; + return this; + } + + public Container build() { + return new Container( + this.items, + this.someItems, + this.specialItems); + } +} diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SomeItemBuilder.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SomeItemBuilder.java new file mode 100644 index 000000000..5be6adbd1 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SomeItemBuilder.java @@ -0,0 +1,35 @@ +package dummy.generation; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Builder for the SomeItem type. + */ +public class SomeItemBuilder { + private String name; + + private String nickname; + + public SomeItemBuilder(String name) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + } + + public SomeItemBuilder setNickname(String nickname) { + this.nickname = nickname; + return this; + } + + public SomeItem build() { + return new SomeItem( + this.name, + this.nickname); + } +} diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SpecialItemBuilder.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SpecialItemBuilder.java new file mode 100644 index 000000000..6daf214e2 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/generation/SpecialItemBuilder.java @@ -0,0 +1,43 @@ +package dummy.generation; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +/** + * Builder for the SpecialItem type. + */ +public class SpecialItemBuilder { + private String name; + + private String nickname; + + private Long level; + + public SpecialItemBuilder( + String name, + Long level) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + this.level = Objects.requireNonNull( + level, + "Argument \"level\" must be non-null."); + } + + public SpecialItemBuilder setNickname(String nickname) { + this.nickname = nickname; + return this; + } + + public SpecialItem build() { + return new SpecialItem( + this.name, + this.level, + this.nickname); + } +} diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/jsonization/Jsonization.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/jsonization/Jsonization.java new file mode 100644 index 000000000..3607b9e9f --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/jsonization/Jsonization.java @@ -0,0 +1,1050 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.jsonization; + +import dummy.common.*; +import dummy.reporting.Reporting; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import dummy.stringification.Stringification; +import dummy.visitation.AbstractTransformer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; + +/** + * Provide de/serialization of meta-model classes to/from JSON. + * + *

We can not use one-pass deserialization for JSON since the object + * properties do not have fixed order, and hence we can not read + * {@code modelType} property ahead of the remaining properties. + */ +public class Jsonization { + /** + * Implement the deserialization of meta-model classes from JSON nodes. + * + *

The implementation propagates an {@link Reporting.Error} instead + * of relying on exceptions. Under the assumption that incorrect data is much + * less frequent than correct data, this makes the deserialization more + * efficient. + * + * However, we do not want to force the client to deal with + * the {@link Reporting.Error} class as this is not intuitive. Therefore + * we distinguish the implementation, realized in + * {@link _DeserializeImplementation}, and the facade given in + * {@link Deserialize} class. + * + *

Every value is parsed through a function which takes a single + * {@link JsonNode} and gives back a {@link Reporting.Result}, so that a list + * and a tuple can be composed out of the parsers of their items. Only they + * need such a composition -- every other value already has a function named + * after its very type. + */ + private static class _DeserializeImplementation { + /** Convert {@code value} to a string. + * @param node JSON node to be parsed + */ + private static Reporting.Result tryStringFrom(JsonNode value) { + if (!value.isTextual()) { + final Reporting.Error error = new Reporting.Error( + "Expected a JsonValue of String, but got " + value.getNodeType()); + return Reporting.Result.failure(error); + } + return Reporting.Result.success(value.asText()); + } + + /** Convert {@code value} to a boolean. + * @param node JSON node to be parsed + */ + private static Reporting.Result tryBooleanFrom(JsonNode value) { + if (!value.isBoolean()) { + final Reporting.Error error = new Reporting.Error( + "Expected a JsonValue of Boolean, but got " + value.getNodeType()); + return Reporting.Result.failure(error); + } + return Reporting.Result.success(value.asBoolean()); + } + + /** Convert {@code value} to a long 64-bit integer. + * @param node JSON node to be parsed + */ + private static Reporting.Result tryLongFrom(JsonNode value) { + if (!value.isIntegralNumber()) { + final Reporting.Error error = new Reporting.Error( + "Expected a JsonValue of Long, but got " + value.getNodeType()); + return Reporting.Result.failure(error); + } + return Reporting.Result.success(value.asLong()); + } + + /** Convert {@code value} to a double-precision 64-bit float. + * @param node JSON node to be parsed + */ + private static Reporting.Result tryDoubleFrom(JsonNode value) { + // NOTE (mristin): + // We deliberately ask for a number, and not for a floating-point number. + // JSON has a single number type, so ``3`` is every bit as good a double as + // ``3.0`` is, and every other SDK reads it as one. + if (!value.isNumber()) { + final Reporting.Error error = new Reporting.Error( + "Expected a JsonValue of Double, but got " + value.getNodeType()); + return Reporting.Result.failure(error); + } + + // NOTE (mristin): + // JSON knows neither an infinity nor a not-a-number, so a conformant parser + // can never give us one. Jackson parses them when + // JsonReadFeature.ALLOW_NON_NUMERIC_NUMBERS is enabled, and the caller can + // construct such a node programmatically in any case, so we check here. + final double asDouble = value.asDouble(); + if (!Double.isFinite(asDouble)) { + final Reporting.Error error = new Reporting.Error( + "Expected a finite number, but got " + asDouble); + return Reporting.Result.failure(error); + } + + return Reporting.Result.success(asDouble); + } + + private static Reporting.Result tryBytesFrom(JsonNode value) { + if (!value.isTextual()) { + final Reporting.Error error = new Reporting.Error( + "Expected a JsonValue of String, but got " + value.getNodeType()); + return Reporting.Result.failure(error); + } + final byte[] decodedData; + Base64.Decoder decoder = Base64.getDecoder(); + + try { + decodedData = decoder.decode(value.textValue()); + } catch (Exception exception) { + final Reporting.Error error = new Reporting.Error( + "Expected Base-64 encoded bytes, but the conversion failed " + + "because: " + exception.getMessage()); + return Reporting.Result.failure(error); + } + + return Reporting.Result.success(decodedData); + } + + /** + * Mark the error of {@code result} as coming from the property {@code name}. + * + *

A {@code case} of a property loop is matched exactly when the key of + * the property equals its literal, so the key already names the property and + * no {@code case} has to spell it out a second time. + */ + private static Reporting.Result prependName( + Reporting.Result result, String name) { + final Reporting.Error error = result.getError(); + error.prependSegment(new Reporting.NameSegment(name)); + return Reporting.Result.failure(error); + } + + /** + * Mark the error of {@code result} as coming from the item at {@code index}. + */ + private static Reporting.Result prependIndex( + Reporting.Result result, int index) { + final Reporting.Error error = result.getError(); + error.prependSegment(new Reporting.IndexSegment(index)); + return Reporting.Result.failure(error); + } + + /** + * Report that {@code node} is no JSON object. + */ + private static Reporting.Result notAJsonObject(JsonNode node) { + return Reporting.Result.failure( + new Reporting.Error( + "Expected a JsonObject, but got " + + (node == null ? "null" : node.getNodeType()))); + } + + /** + * Report that {@code node} is no JSON array. + */ + private static Reporting.Result notAJsonArray(JsonNode node) { + return Reporting.Result.failure( + new Reporting.Error( + "Expected a JsonArray, but got " + node.getNodeType())); + } + + /** + * Report a property which the class does not have. + */ + private static Reporting.Result unexpectedProperty(String name) { + return Reporting.Result.failure( + new Reporting.Error("Unexpected property: " + name)); + } + + /** + * Report a required property which the JSON object did not give. + */ + private static Reporting.Result missingRequiredProperty(String name) { + return Reporting.Result.failure( + new Reporting.Error( + "Required property \"" + name + "\" is missing")); + } + + /** + * Extract the {@code modelType} property of {@code node} as a string. + * + *

This is the only place which knows how the model type is spelled on + * the wire. Both the dispatch on the model type and its check in a concrete + * class go through it. + * + * @param node JSON object to be inspected + */ + private static Reporting.Result tryModelTypeFrom(JsonNode node) { + final JsonNode modelTypeNode = node.get("modelType"); + if (modelTypeNode == null) { + return missingRequiredProperty("modelType"); + } + + final Reporting.Result result = tryStringFrom(modelTypeNode); + if (result.isError()) { + return prependName(result, "modelType"); + } + + return result; + } + + /** + * Check that {@code node} gives the {@code expected} model type, and return + * the error if it does not. + * + *

The model type is checked before the properties are read, so that a wrong + * one is reported without de-serializing any of them first, and so that + * the property loop carries nothing but the properties. + * + * @param node JSON object to be inspected + * @param expected model type of the class being de-serialized + */ + private static Reporting.Error checkModelType(JsonNode node, String expected) { + final Reporting.Result result = tryModelTypeFrom(node); + if (result.isError()) { + return result.getError(); + } + + final String modelType = result.getResult(); + if (!modelType.equals(expected)) { + final Reporting.Error error = new Reporting.Error( + "Expected the model type '" + expected + "', " + + "but got '" + modelType + "'"); + // NOTE (mristin): + // A wrong model type is reported at the instance, as an unknown one is, + // and as in the other SDKs. + return error; + } + + return null; + } + + /** + * Parse {@code node} as a JSON array, and every of its items with + * {@code parseItem}. + * + * @param node JSON node to be parsed + * @param parseItem to parse a single item of the array + */ + private static Reporting.Result> parseArray( + JsonNode node, + Function> parseItem) { + if (!node.isArray()) { + return notAJsonArray(node); + } + + final List result = new ArrayList<>(node.size()); + + int index = 0; + for (JsonNode item : node) { + final Reporting.Result parsedItem = parseItem.apply(item); + if (parsedItem.isError()) { + return prependIndex(parsedItem, index); + } + + result.add(parsedItem.getResult()); + index++; + } + + return Reporting.Result.success(result); + } + + /** + * Parse {@code node} as a list of {@code IItem}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result> parseListOf_IItem(JsonNode node) { + return parseArray(node, _DeserializeImplementation::tryIItemFrom); + } + + /** + * Parse {@code node} as a list of {@code ISomeItem}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result> parseListOf_ISomeItem(JsonNode node) { + return parseArray(node, _DeserializeImplementation::tryISomeItemFrom); + } + + /** + * Parse {@code node} as a list of {@code ISpecialItem}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result> parseListOf_ISpecialItem(JsonNode node) { + return parseArray(node, _DeserializeImplementation::trySpecialItemFrom); + } + + /** + * Deserialize an instance of IItem by dispatching + * based on {@code modelType} property of the {@code node}. + * + * @param node JSON node to be parsed + */ + public static Reporting.Result tryIItemFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + final Reporting.Result modelTypeResult = tryModelTypeFrom(node); + if (modelTypeResult.isError()) { + return modelTypeResult.castTo(IItem.class); + } + + switch (modelTypeResult.getResult()) { + case "SomeItem": + return trySomeItemFromObject(node); + case "SpecialItem": + return trySpecialItemFromObject(node); + default: { + final Reporting.Error error = new Reporting.Error( + "Unexpected model type for IItem: " + modelTypeResult.getResult()); + return Reporting.Result.failure(error); + } + } + } + + /** + * Deserialize an instance of ISomeItem by dispatching + * based on {@code modelType} property of the {@code node}. + * + * @param node JSON node to be parsed + */ + public static Reporting.Result tryISomeItemFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + final Reporting.Result modelTypeResult = tryModelTypeFrom(node); + if (modelTypeResult.isError()) { + return modelTypeResult.castTo(ISomeItem.class); + } + + switch (modelTypeResult.getResult()) { + case "SpecialItem": + return trySpecialItemFromObject(node); + case "SomeItem": + return trySomeItemFromObject(node); + default: { + final Reporting.Error error = new Reporting.Error( + "Unexpected model type for ISomeItem: " + modelTypeResult.getResult()); + return Reporting.Result.failure(error); + } + } + } + + /** + * Deserialize an instance of SomeItem from {@code node}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result trySomeItemFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + final Reporting.Error modelTypeError = checkModelType(node, "SomeItem"); + if (modelTypeError != null) { + return Reporting.Result.failure(modelTypeError); + } + + return trySomeItemFromObject(node); + } + + /** + * Deserialize an instance of SomeItem from the JSON object {@code node} whose + * model type has already been checked. + * + * @param node JSON object to be parsed + */ + private static Reporting.Result trySomeItemFromObject(JsonNode node) { + String theName = null; + String theNickname = null; + + for (Iterator> iterator = node.fields(); iterator.hasNext(); ) { + final Map.Entry keyValue = iterator.next(); + final String key = keyValue.getKey(); + final JsonNode value = keyValue.getValue(); + + switch (key) { + case "name": { + final Reporting.Result parsed = tryStringFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theName = parsed.getResult(); + break; + } + case "nickname": { + final Reporting.Result parsed = tryStringFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theNickname = parsed.getResult(); + break; + } + case "modelType": + // The model type has already been checked before the loop. + break; + default: + return unexpectedProperty(key); + } + } + + if (theName == null) { + return missingRequiredProperty("name"); + } + + return Reporting.Result.success(new SomeItem( + theName, + theNickname)); + } + + /** + * Deserialize an instance of SpecialItem from {@code node}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result trySpecialItemFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + final Reporting.Error modelTypeError = checkModelType(node, "SpecialItem"); + if (modelTypeError != null) { + return Reporting.Result.failure(modelTypeError); + } + + return trySpecialItemFromObject(node); + } + + /** + * Deserialize an instance of SpecialItem from the JSON object {@code node} whose + * model type has already been checked. + * + * @param node JSON object to be parsed + */ + private static Reporting.Result trySpecialItemFromObject(JsonNode node) { + String theName = null; + Long theLevel = null; + String theNickname = null; + + for (Iterator> iterator = node.fields(); iterator.hasNext(); ) { + final Map.Entry keyValue = iterator.next(); + final String key = keyValue.getKey(); + final JsonNode value = keyValue.getValue(); + + switch (key) { + case "name": { + final Reporting.Result parsed = tryStringFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theName = parsed.getResult(); + break; + } + case "level": { + final Reporting.Result parsed = tryLongFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theLevel = parsed.getResult(); + break; + } + case "nickname": { + final Reporting.Result parsed = tryStringFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theNickname = parsed.getResult(); + break; + } + case "modelType": + // The model type has already been checked before the loop. + break; + default: + return unexpectedProperty(key); + } + } + + if (theName == null) { + return missingRequiredProperty("name"); + } + + if (theLevel == null) { + return missingRequiredProperty("level"); + } + + return Reporting.Result.success(new SpecialItem( + theName, + theLevel, + theNickname)); + } + + /** + * Deserialize an instance of Container from {@code node}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result tryContainerFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + List theItems = null; + List theSomeItems = null; + List theSpecialItems = null; + + for (Iterator> iterator = node.fields(); iterator.hasNext(); ) { + final Map.Entry keyValue = iterator.next(); + final String key = keyValue.getKey(); + final JsonNode value = keyValue.getValue(); + + switch (key) { + case "items": { + final Reporting.Result> parsed = parseListOf_IItem(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theItems = parsed.getResult(); + break; + } + case "someItems": { + final Reporting.Result> parsed = parseListOf_ISomeItem(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theSomeItems = parsed.getResult(); + break; + } + case "specialItems": { + final Reporting.Result> parsed = parseListOf_ISpecialItem(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theSpecialItems = parsed.getResult(); + break; + } + default: + return unexpectedProperty(key); + } + } + + if (theItems == null) { + return missingRequiredProperty("items"); + } + + if (theSomeItems == null) { + return missingRequiredProperty("someItems"); + } + + return Reporting.Result.success(new Container( + theItems, + theSomeItems, + theSpecialItems)); + } + + /** + * Deserialize an instance of Wrapper from {@code node}. + * + * @param node JSON node to be parsed + */ + private static Reporting.Result tryWrapperFrom(JsonNode node) { + if (node == null || !node.isObject()) { + return notAJsonObject(node); + } + + IContainer theContainer = null; + + for (Iterator> iterator = node.fields(); iterator.hasNext(); ) { + final Map.Entry keyValue = iterator.next(); + final String key = keyValue.getKey(); + final JsonNode value = keyValue.getValue(); + + switch (key) { + case "container": { + final Reporting.Result parsed = tryContainerFrom(value); + if (parsed.isError()) { + return prependName(parsed, key); + } + theContainer = parsed.getResult(); + break; + } + default: + return unexpectedProperty(key); + } + } + + if (theContainer == null) { + return missingRequiredProperty("container"); + } + + return Reporting.Result.success(new Wrapper( + theContainer)); + } + } + + /** + * Represent a critical error during the deserialization. + */ + @SuppressWarnings("serial") + public static class DeserializeException extends RuntimeException { + private final String path; + private final String reason; + + public DeserializeException(String path, String reason) { + super(reason + " at: " + ("".equals(path) ? "the beginning" : path)); + this.path = path; + this.reason = reason; + } + + public Optional getPath() { + return Optional.ofNullable(path); + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + } + + /** + * Represent a critical error during the serialization. + */ + @SuppressWarnings("serial") + public static class SerializeException extends RuntimeException { + private final String path; + private final String reason; + + public SerializeException(String path, String reason) { + super(reason + " at: " + ("".equals(path) ? "the beginning" : path)); + this.path = path; + this.reason = reason; + } + + public Optional getPath() { + return Optional.ofNullable(path); + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + } + + /** + * Signal a failure of the serialization, carrying the path to the culprit. + * + *

The path is built as the stack unwinds -- every container prepends + * the one segment it knows, the property its name and the list the index + * of the item -- which is why this can not be a + * {@link SerializeException} already: that one renders its message in + * its constructor, so its path has to be complete by then. + * {@link Serialize#toJsonObject} renders and converts. + */ + @SuppressWarnings("serial") + private static class _SerializeFailure extends RuntimeException { + private final Reporting.Error error; + + _SerializeFailure(Reporting.Error error) { + super(error.getCause()); + this.error = error; + } + + Reporting.Error getError() { + return error; + } + } + + /** + * Deserialize instances of meta-model classes from JSON nodes. + * + * Here is an example how to parse an instance of IItem: + *

{@code
+     * String someString = "... some JSON ...";
+     * ObjectMapper objectMapper = new ObjectMapper();
+     * JsonNode node = objectMapper.readTree(someString);
+     * IItem anInstance = Deserialize.deserializeIItem(
+     *   node);
+     * }
+ */ + public static class Deserialize + { + /** + * Deserialize an instance of IItem from {@code node}. + * + * @param node JSON node to be parsed + */ + public static IItem deserializeIItem(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.tryIItemFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of ISomeItem from {@code node}. + * + * @param node JSON node to be parsed + */ + public static ISomeItem deserializeISomeItem(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.tryISomeItemFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of SomeItem from {@code node}. + * + * @param node JSON node to be parsed + */ + public static SomeItem deserializeSomeItem(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.trySomeItemFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of SpecialItem from {@code node}. + * + * @param node JSON node to be parsed + */ + public static SpecialItem deserializeSpecialItem(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.trySpecialItemFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of Container from {@code node}. + * + * @param node JSON node to be parsed + */ + public static Container deserializeContainer(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.tryContainerFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of Wrapper from {@code node}. + * + * @param node JSON node to be parsed + */ + public static Wrapper deserializeWrapper(JsonNode node) { + final Reporting.Result result = + _DeserializeImplementation.tryWrapperFrom( + node); + + return result.onError(error -> { + throw new DeserializeException( + Reporting.generateJsonPath(error.getPathSegments()), + error.getCause()); + }); + } + } + + private static class _Transformer extends AbstractTransformer { + /** + * Dispatch the serialization over the run-time type of an instance. + * + *

The transformer carries no state, so a single instance serves + * the whole program. + */ + private static final _Transformer INSTANCE = new _Transformer(); + + /** + * Serialize {@code that} into a JSON object. + * + *

Which JSON object that is, is decided by the run-time type of + * {@code that}, so this one serializer serves every abstract class, every + * concrete class with descendants, and the item of a list or of a tuple of + * any class at all. The de-serialization, which has to decide what to + * construct before it has read anything, needs a dispatcher per interface + * instead. + * + *

It is static, so that a composed serializer -- which is static as well, + * since it carries no state either -- can reach it. + */ + static JsonNode transformClass(IClass that) { + return INSTANCE.transform(that); + } + + /** + * Convert a single value into a JSON node. + */ + @FunctionalInterface + private interface Serializer { + JsonNode serialize(T that); + } + + /** + * Set the property {@code jsonName} of {@code result} to + * {@code that}, serialized by {@code serialize}. + * + *

{@code getterName} names the property on the path of a failure. It is + * the getter, and not the JSON property: a serialization error is reported + * on an instance, which the caller holds, and not on a document + * which has not been written yet. + */ + private static void setProperty( + ObjectNode result, + String jsonName, + String getterName, + T that, + Serializer serialize) { + try { + result.set(jsonName, serialize.serialize(that)); + } catch (_SerializeFailure failure) { + failure.getError().prependSegment( + new Reporting.NameSegment(getterName)); + throw failure; + } + } + + /** + * Set the property {@code jsonName} of {@code result} if {@code that} + * has been given, and set nothing at all otherwise. + * + *

The {@link Optional} is taken apart here, once, instead of at every + * optional property: asking it and then unwrapping it at the call site would + * call the getter twice, and every call allocates an {@link Optional} of + * its own. + */ + private static void setOptionalProperty( + ObjectNode result, + String jsonName, + String getterName, + Optional that, + Serializer serialize) { + final T value = that.orElse(null); + if (value != null) { + setProperty(result, jsonName, getterName, value, serialize); + } + } + + /** + * Convert {@code that} string to a JSON value. + * + *

See the note on {@link #boolToJsonNode} on why this wrapper exists. + * + * @param that value to be converted + */ + private static JsonNode stringToJsonNode(String that) { + return JsonNodeFactory.instance.textNode(that); + } + + /** + * Convert {@code that} 64-bit long integer to a JSON value. + * + * @param that value to be converted + */ + private static JsonNode longToJsonNode(Long that) { + // NOTE (mristin): + // Outside this range an integer can not be exactly represented as a 64-bit + // floating-point number, which is what the JSON de-serializers of the other + // languages read a number into. + final long primitiveThat = that.longValue(); + if (primitiveThat < -9007199254740991L || primitiveThat > 9007199254740991L) { + throw new _SerializeFailure( + new Reporting.Error( + "The integer can not be serialized to JSON as it is outside " + + "the range [-2^53 + 1, 2^53 - 1]: " + that)); + } + return JsonNodeFactory.instance.numberNode(that); + } + + /** + * Serialize every item of {@code that} into a JSON array. + * + * @param that to be serialized + */ + private static ArrayNode serializeListOf_IClass( + List that) { + final ArrayNode result = JsonNodeFactory.instance.arrayNode(); + int i = 0; + for (IClass item : that) { + try { + result.add(transformClass(item)); + } catch (_SerializeFailure failure) { + failure.getError().prependSegment( + new Reporting.IndexSegment(i)); + throw failure; + } + i++; + } + return result; + } + + @Override + public JsonNode transformSomeItem( + ISomeItem that + ) { + final ObjectNode result = JsonNodeFactory.instance.objectNode(); + + setProperty(result, "name", "getName()", that.getName(), _Transformer::stringToJsonNode); + + setOptionalProperty( + result, "nickname", "getNickname()", + that.getNickname(), _Transformer::stringToJsonNode); + + result.put("modelType", "SomeItem"); + + return result; + } + + @Override + public JsonNode transformSpecialItem( + ISpecialItem that + ) { + final ObjectNode result = JsonNodeFactory.instance.objectNode(); + + setProperty(result, "name", "getName()", that.getName(), _Transformer::stringToJsonNode); + + setOptionalProperty( + result, "nickname", "getNickname()", + that.getNickname(), _Transformer::stringToJsonNode); + + setProperty(result, "level", "getLevel()", that.getLevel(), _Transformer::longToJsonNode); + + result.put("modelType", "SpecialItem"); + + return result; + } + + @Override + public JsonNode transformContainer( + IContainer that + ) { + final ObjectNode result = JsonNodeFactory.instance.objectNode(); + + setProperty( + result, "items", "getItems()", + that.getItems(), _Transformer::serializeListOf_IClass); + + setProperty( + result, "someItems", "getSomeItems()", + that.getSomeItems(), _Transformer::serializeListOf_IClass); + + setOptionalProperty( + result, "specialItems", "getSpecialItems()", + that.getSpecialItems(), _Transformer::serializeListOf_IClass); + + return result; + } + + @Override + public JsonNode transformWrapper( + IWrapper that + ) { + final ObjectNode result = JsonNodeFactory.instance.objectNode(); + + setProperty( + result, "container", "getContainer()", + that.getContainer(), _Transformer::transformClass); + + return result; + } + } + + /** + * Serialize instances of meta-model classes to JSON elements. + * + * Here is an example how to serialize an instance of IItem: + *

{@code
+     * IItem anInstance = new IItem(
+     *     // ... some constructor arguments ...
+     * );
+     * JsonNode element = Jsonization.Serialize.toJsonObject(
+     *     anInstance));
+     * }
+ */ + public static class Serialize + { + /** + * Serialize an instance of the meta-model into a JSON object. + * + * @throws SerializeException if a value within {@code that} instance can + * not be represented in JSON + */ + public static JsonNode toJsonObject(IClass that) { + try { + return _Transformer.transformClass(that); + } catch (_SerializeFailure failure) { + final Reporting.Error error = failure.getError(); + throw new SerializeException( + Reporting.generateJavaPath(error.getPathSegments()), + error.getCause()); + } + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/reporting/Reporting.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/reporting/Reporting.java new file mode 100644 index 000000000..647e421f2 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/reporting/Reporting.java @@ -0,0 +1,345 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.reporting; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.Collections; +import java.util.Collection; +import java.util.List; +import java.util.LinkedList; +import java.util.Objects; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Provide reporting for de/serialization and verification. + */ +public class Reporting +{ + /** + * Capture a path segment of a value in a model. + */ + public static abstract class Segment { + // Intentionally empty. + } + + public static class NameSegment extends Segment { + private final String name; + public NameSegment(String name) { + this.name = Objects.requireNonNull(name, + "Argument \"name\" must be non-null."); + } + public String getName(){ + return name; + } + } + + public static class IndexSegment extends Segment{ + private final Integer index; + public IndexSegment(int index) { + this.index = index; + } + public Integer getIndex(){ + return index; + } + } + + /** + * Capture a member of an open JSON-able object on a path to + * the erroneous value. + * + *

Unlike a {@link NameSegment}, which names a property of one of our + * classes, a key is known only at run time, and can be any string at all. + */ + public static class KeySegment extends Segment { + private final String key; + public KeySegment(String key) { + this.key = Objects.requireNonNull(key, + "Argument \"key\" must be non-null."); + } + public String getKey(){ + return key; + } + } + + private static final Pattern variableNameRe = Pattern.compile("^[a-zA-Z_][a-zA-Z_0-9]*$"); + + /** + * Generate a JSON Path based on the path segments. + * + *

See, for example, this page for more information on JSON path: + * https://support.smartbear.com/alertsite/docs/monitors/api/endpoint/jsonpath.html + */ + public static String generateJsonPath(Collection segments) { + ArrayList parts = new ArrayList<>(segments.size()); + int i = 0; + + for (Segment segment : segments) { + String part; + + if (segment instanceof NameSegment) { + NameSegment nameSegment = (NameSegment) segment; + Matcher m = variableNameRe.matcher(nameSegment.getName()); + + if (m.matches()) { + part = (i == 0) ? nameSegment.getName() : "." + nameSegment.getName(); + } else { + part = "[\"" + escapeForJsonString(nameSegment.getName()) + "\"]"; + } + } else if (segment instanceof IndexSegment) { + IndexSegment indexSegment = (IndexSegment) segment; + part = "[" + indexSegment.getIndex() + "]"; + } else if (segment instanceof KeySegment) { + // A key is no name of a property of one of our classes, so it is + // always bracketed, whatever it looks like. + KeySegment keySegment = (KeySegment) segment; + part = "[\"" + escapeForJsonString(keySegment.getKey()) + "\"]"; + } else { + throw new RuntimeException( + "Unexpected segment type: " + segment.getClass().getSimpleName() + ); + } + + parts.add(part); + i++; + } + return String.join("", parts); + } + + /** + * Escape the characters which a JSON string may not hold as they are. + * + *

The control characters without a short escape are written as Unicode + * escapes, as {@code JSON.stringify} does. + */ + private static String escapeForJsonString(String text) { + StringBuilder builder = new StringBuilder(text.length()); + for (int i = 0; i < text.length(); i++) { + char character = text.charAt(i); + switch (character) { + case '\\': + builder.append("\\\\"); + break; + case '"': + builder.append("\\\""); + break; + case '\b': + builder.append("\\b"); + break; + case '\f': + builder.append("\\f"); + break; + case '\n': + builder.append("\\n"); + break; + case '\r': + builder.append("\\r"); + break; + case '\t': + builder.append("\\t"); + break; + default: + if (character < 0x20) { + builder.append(String.format("\\u%04x", (int) character)); + } else { + builder.append(character); + } + break; + } + } + return builder.toString(); + } + + /** + * Escape special characters for XPath. + */ + private static String escapeForXPath(String text) { + return text + .replace("&", "&") + .replace("/", "/") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + + /** + * Generate a relative XPath based on the path segments. + * + *

This method leaves out the leading slash ('/'). This is helpful if + * to embed the error report in a larger document with a prefix etc. + */ + public static String generateRelativeXPath(Collection segments) { + final List parts = new ArrayList<>(); + segments.forEach(segment -> { + String part; + if (segment instanceof NameSegment) { + final NameSegment nameSegment = ((NameSegment) segment); + final String name = nameSegment.getName(); + part = escapeForXPath(name); + } else if (segment instanceof IndexSegment) { + final IndexSegment indexSegment = ((IndexSegment) segment); + // NOTE (mristin): + // XPath counts the elements starting at one, while our indices start at zero. + final int index = indexSegment.getIndex(); + part = "*[" + (index + 1) + "]"; + } else if (segment instanceof KeySegment) { + // A JSON-able object is written as an XML-RPC , which holds + // the key of a member in a child element, and not in + // an attribute, so the XPath has to match on that child element. + final KeySegment keySegment = ((KeySegment) segment); + part = "member[name=\"" + + escapeForXPath(keySegment.getKey()) + "\"]"; + } else { + throw new IllegalArgumentException("Unexpected segment type: " + + segment.getClass().getSimpleName()); + } + parts.add(part); + }); + return String.join("/", parts); + } + + /** + * Generate a Java access path based on the path segments. + * + *

The name segments are expected to be the getters of the properties in + * Java, parentheses included. This is the path to report where in an + * instance something went wrong -- on the serialization, say, + * where the caller holds the instance, and not a document which has not + * been written yet. + * + *

Unlike the JSON path and the XPath, this one is a Java expression on + * that instance which the caller can paste, as a list and a JSON-able value + * happen to agree on {@code get}: an item of a list, an item of + * a JSON-able array and a member of a JSON-able object are all reached with + * it, e.g., + * {@code getSubmodelElements().get(0).getValue().get("some key")}. + */ + public static String generateJavaPath(Collection segments) { + final List parts = new ArrayList<>(segments.size()); + int i = 0; + + for (Segment segment : segments) { + final String part; + + if (segment instanceof NameSegment) { + final NameSegment nameSegment = ((NameSegment) segment); + part = (i == 0) + ? nameSegment.getName() + : "." + nameSegment.getName(); + } else if (segment instanceof IndexSegment) { + final IndexSegment indexSegment = ((IndexSegment) segment); + part = ".get(" + indexSegment.getIndex() + ")"; + } else if (segment instanceof KeySegment) { + final KeySegment keySegment = ((KeySegment) segment); + part = ".get(\"" + escapeForJsonString(keySegment.getKey()) + "\")"; + } else { + throw new IllegalArgumentException("Unexpected segment type: " + + segment.getClass().getSimpleName()); + } + + parts.add(part); + i++; + } + return String.join("", parts); + } + + /** + * Represent an error during the deserialization or the verification. + */ + public static class Error { + private final Deque pathSegments = new LinkedList<>(); + private final String cause; + + public Error(String cause) { + this.cause = cause; + } + + public void prependSegment(Segment segment) { + pathSegments.addFirst(segment); + } + + public String getCause() { + return cause; + } + + public Collection getPathSegments() { + return pathSegments; + } + } + + /** + * Represent the outcome of a de/serialization or a verification step. + * + *

This is shared by JSON de/serialization, XML de/serialization and + * verification, all of which propagate an {@link Error} instead of relying + * on exceptions for the common (successful) case. + */ + public static class Result { + private final T result; + private final Error error; + private final boolean success; + + private Result(T result, Error error, boolean success) { + this.result = result; + this.error = error; + this.success = success; + } + + public static Result success(T result) { + if (result == null) throw new IllegalArgumentException("Result must not be null."); + return new Result<>(result, null, true); + } + + public static Result failure(Error error) { + if (error == null) throw new IllegalArgumentException("Error must not be null."); + return new Result<>(null, error, false); + } + + @SuppressWarnings("unchecked") + public Result castTo(Class type) { + if (isError() || type.isInstance(result)) return (Result) this; + throw new IllegalStateException("Result of type " + + result.getClass().getName() + + " is not an instance of " + + type.getName()); + } + + public T getResult() { + if (!isSuccess()) throw new IllegalStateException("Result is not present."); + return result; + } + + public boolean isSuccess() { + return success; + } + + public boolean isError() { + return !success; + } + + public Error getError() { + if (isSuccess()) throw new IllegalStateException("Result is present."); + return error; + } + + public R map(Function successFunction, Function errorFunction) { + return isSuccess() ? successFunction.apply(result) : errorFunction.apply(error); + } + + public T onError(Function errorFunction) { + return map(Function.identity(), errorFunction); + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/stringification/Stringification.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/stringification/Stringification.java new file mode 100644 index 000000000..f38a60c28 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/stringification/Stringification.java @@ -0,0 +1,22 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.stringification; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import dummy.types.enums.*; + +public class Stringification { + +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/enums/IEnum.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/enums/IEnum.java new file mode 100644 index 000000000..3efe6c3f3 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/enums/IEnum.java @@ -0,0 +1,24 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.enums; + +/** + * Represent a literal of an enumeration of the meta-model. + */ +public interface IEnum { + /** + * Get the string representation of this literal. + * + *

This is the text the literal is de/serialized as, which is in general + * not its {@link Enum#name()}. + */ + String literalText(); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Container.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Container.java new file mode 100644 index 000000000..bbf25428b --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Container.java @@ -0,0 +1,261 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.impl; + +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Objects; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import dummy.types.model.IContainer; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Container implements IContainer { + private List items; + + private List someItems; + + private List specialItems; + + public Container( + List items, + List someItems) { + this.items = Objects.requireNonNull( + items, + "Argument \"items\" must be non-null."); + this.someItems = Objects.requireNonNull( + someItems, + "Argument \"someItems\" must be non-null."); + } + + public Container( + List items, + List someItems, + List specialItems) { + this.items = Objects.requireNonNull( + items, + "Argument \"items\" must be non-null."); + this.someItems = Objects.requireNonNull( + someItems, + "Argument \"someItems\" must be non-null."); + this.specialItems = specialItems; + } + + @Override + public List getItems() { + return items; + } + + @Override + public void setItems(List items) { + this.items = Objects.requireNonNull( + items, + "Argument \"items\" must be non-null."); + } + + @Override + public List getSomeItems() { + return someItems; + } + + @Override + public void setSomeItems(List someItems) { + this.someItems = Objects.requireNonNull( + someItems, + "Argument \"someItems\" must be non-null."); + } + + @Override + public Optional> getSpecialItems() { + return Optional.ofNullable(specialItems); + } + + @Override + public void setSpecialItems(List specialItems) { + this.specialItems = specialItems; + } + + /** + * Iterate over {@link Container#specialItems}, if set, + * and otherwise return an empty iterator. + */ + public Iterable overSpecialItemsOrEmpty() { + return getSpecialItems().orElseGet(Collections::emptyList); + } + + /** + * Count the {@code items}. + * + *

The arguments of the methods are never generic, so only the sequences of + * {@link dummy.types.model.IItem} can be passed to it, but not the sequences of + * the descendants. + */ + @Override + public Long countItems( + List items + ) { + return (long) items.size(); + } + + /** + * Iterate recursively over all the class instances referenced from this instance. + */ + public Iterable descend() { + return new _ContainerRecursiveIterable(); + } + + /** + * Iterate over all the class instances referenced from this instance. + */ + public Iterable descendOnce() { + return new _ContainerIterable(); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch. + **/ + @Override + public void accept(IVisitor visitor) { + visitor.visitContainer(this); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public void accept( + IVisitorWithContext visitor, + ContextT context) { + visitor.visitContainer(this, context); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch. + **/ + @Override + public T transform(ITransformer transformer) { + return transformer.transformContainer(this); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public T transform( + ITransformerWithContext transformer, + ContextT context) { + return transformer.transformContainer(this, context); + } + + private class _ContainerIterable implements Iterable { + @Override + public Iterator iterator() { + Stream stream = stream(); + + return stream.iterator(); + } + + @Override + public void forEach(Consumer action) { + Stream stream = stream(); + + stream.forEach(action); + } + + @Override + public Spliterator spliterator() { + Stream stream = stream(); + + return stream.spliterator(); + } + + private Stream stream() { + Stream memberStream = Stream.empty(); + + if (items != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_IItem(Container.this.items, false)); + } + + if (someItems != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_ISomeItem(Container.this.someItems, false)); + } + + if (specialItems != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_ISpecialItem(Container.this.specialItems, false)); + } + + return memberStream; + } + } + + private class _ContainerRecursiveIterable implements Iterable { + @Override + public Iterator iterator() { + Stream stream = stream(); + + return stream.iterator(); + } + + @Override + public void forEach(Consumer action) { + Stream stream = stream(); + + stream.forEach(action); + } + + @Override + public Spliterator spliterator() { + Stream stream = stream(); + + return stream.spliterator(); + } + + private Stream stream() { + Stream memberStream = Stream.empty(); + + if (items != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_IItem(Container.this.items, true)); + } + + if (someItems != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_ISomeItem(Container.this.someItems, true)); + } + + if (specialItems != null) { + memberStream = Stream.concat(memberStream, + Descent.descendListOf_ISpecialItem(Container.this.specialItems, true)); + } + + return memberStream; + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Descent.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Descent.java new file mode 100644 index 000000000..c9f078a8f --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Descent.java @@ -0,0 +1,84 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.impl; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.model.*; +import java.util.List; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Descend into the containers which hold class instances. + * + *

Each method descends only one level, and calls the method of the items + * by name, so that the descent is composed of plain functions to any depth. + */ +final class Descent { + private Descent() { + // Prevent instantiation + } + + /** + * Iterate over the class instances held by {@code that}. + * + *

If {@code recurse} is set, descend recursively into the instances + * as well. + */ + static Stream descendListOf_IItem( + List that, + boolean recurse) { + if (!recurse) { + return that.stream().map(item -> item); + } + + return that.stream().flatMap(item -> + Stream.concat(Stream.of(item), + StreamSupport.stream(item.descend().spliterator(), false))); + } + + /** + * Iterate over the class instances held by {@code that}. + * + *

If {@code recurse} is set, descend recursively into the instances + * as well. + */ + static Stream descendListOf_ISomeItem( + List that, + boolean recurse) { + if (!recurse) { + return that.stream().map(item -> item); + } + + return that.stream().flatMap(item -> + Stream.concat(Stream.of(item), + StreamSupport.stream(item.descend().spliterator(), false))); + } + + /** + * Iterate over the class instances held by {@code that}. + * + *

If {@code recurse} is set, descend recursively into the instances + * as well. + */ + static Stream descendListOf_ISpecialItem( + List that, + boolean recurse) { + if (!recurse) { + return that.stream().map(item -> item); + } + + return that.stream().flatMap(item -> + Stream.concat(Stream.of(item), + StreamSupport.stream(item.descend().spliterator(), false))); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SomeItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SomeItem.java new file mode 100644 index 000000000..d2599933c --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SomeItem.java @@ -0,0 +1,120 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.impl; + +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Objects; +import dummy.types.model.ISomeItem; + +public class SomeItem implements ISomeItem { + private String name; + + private String nickname; + + public SomeItem(String name) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + } + + public SomeItem( + String name, + String nickname) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + this.nickname = nickname; + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + } + + @Override + public Optional getNickname() { + return Optional.ofNullable(nickname); + } + + @Override + public void setNickname(String nickname) { + this.nickname = nickname; + } + + /** + * Iterate recursively over all the class instances referenced from this instance. + */ + public Iterable descend() { + return Collections.emptyList(); + } + + /** + * Iterate over all the class instances referenced from this instance. + */ + public Iterable descendOnce() { + return Collections.emptyList(); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch. + **/ + @Override + public void accept(IVisitor visitor) { + visitor.visitSomeItem(this); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public void accept( + IVisitorWithContext visitor, + ContextT context) { + visitor.visitSomeItem(this, context); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch. + **/ + @Override + public T transform(ITransformer transformer) { + return transformer.transformSomeItem(this); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public T transform( + ITransformerWithContext transformer, + ContextT context) { + return transformer.transformSomeItem(this, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SpecialItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SpecialItem.java new file mode 100644 index 000000000..62b1ede0b --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/SpecialItem.java @@ -0,0 +1,146 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.impl; + +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Objects; +import dummy.types.model.ISpecialItem; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class SpecialItem implements ISpecialItem { + private String name; + + private String nickname; + + private Long level; + + public SpecialItem( + String name, + Long level) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + this.level = Objects.requireNonNull( + level, + "Argument \"level\" must be non-null."); + } + + public SpecialItem( + String name, + Long level, + String nickname) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + this.nickname = nickname; + this.level = Objects.requireNonNull( + level, + "Argument \"level\" must be non-null."); + } + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + this.name = Objects.requireNonNull( + name, + "Argument \"name\" must be non-null."); + } + + @Override + public Optional getNickname() { + return Optional.ofNullable(nickname); + } + + @Override + public void setNickname(String nickname) { + this.nickname = nickname; + } + + @Override + public Long getLevel() { + return level; + } + + @Override + public void setLevel(Long level) { + this.level = Objects.requireNonNull( + level, + "Argument \"level\" must be non-null."); + } + + /** + * Iterate recursively over all the class instances referenced from this instance. + */ + public Iterable descend() { + return Collections.emptyList(); + } + + /** + * Iterate over all the class instances referenced from this instance. + */ + public Iterable descendOnce() { + return Collections.emptyList(); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch. + **/ + @Override + public void accept(IVisitor visitor) { + visitor.visitSpecialItem(this); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public void accept( + IVisitorWithContext visitor, + ContextT context) { + visitor.visitSpecialItem(this, context); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch. + **/ + @Override + public T transform(ITransformer transformer) { + return transformer.transformSpecialItem(this); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public T transform( + ITransformerWithContext transformer, + ContextT context) { + return transformer.transformSpecialItem(this, context); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Wrapper.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Wrapper.java new file mode 100644 index 000000000..b373a45d5 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/impl/Wrapper.java @@ -0,0 +1,176 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.impl; + +import dummy.common.*; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Objects; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import dummy.types.model.IWrapper; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Wrapper implements IWrapper { + private IContainer container; + + public Wrapper(IContainer container) { + this.container = Objects.requireNonNull( + container, + "Argument \"container\" must be non-null."); + } + + @Override + public IContainer getContainer() { + return container; + } + + @Override + public void setContainer(IContainer container) { + this.container = Objects.requireNonNull( + container, + "Argument \"container\" must be non-null."); + } + + /** + * Iterate recursively over all the class instances referenced from this instance. + */ + public Iterable descend() { + return new _WrapperRecursiveIterable(); + } + + /** + * Iterate over all the class instances referenced from this instance. + */ + public Iterable descendOnce() { + return new _WrapperIterable(); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch. + **/ + @Override + public void accept(IVisitor visitor) { + visitor.visitWrapper(this); + } + + /** + * Accept the {@code visitor} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public void accept( + IVisitorWithContext visitor, + ContextT context) { + visitor.visitWrapper(this, context); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch. + **/ + @Override + public T transform(ITransformer transformer) { + return transformer.transformWrapper(this); + } + + /** + * Accept the {@code transformer} to visit this instance for double dispatch + * with the {@code context}. + **/ + @Override + public T transform( + ITransformerWithContext transformer, + ContextT context) { + return transformer.transformWrapper(this, context); + } + + private class _WrapperIterable implements Iterable { + @Override + public Iterator iterator() { + Stream stream = stream(); + + return stream.iterator(); + } + + @Override + public void forEach(Consumer action) { + Stream stream = stream(); + + stream.forEach(action); + } + + @Override + public Spliterator spliterator() { + Stream stream = stream(); + + return stream.spliterator(); + } + + private Stream stream() { + Stream memberStream = Stream.empty(); + + if (container != null) { + memberStream = Stream.concat(memberStream, + Stream.of(Wrapper.this.container)); + } + + return memberStream; + } + } + + private class _WrapperRecursiveIterable implements Iterable { + @Override + public Iterator iterator() { + Stream stream = stream(); + + return stream.iterator(); + } + + @Override + public void forEach(Consumer action) { + Stream stream = stream(); + + stream.forEach(action); + } + + @Override + public Spliterator spliterator() { + Stream stream = stream(); + + return stream.spliterator(); + } + + private Stream stream() { + Stream memberStream = Stream.empty(); + + if (container != null) { + memberStream = Stream.concat(memberStream, + Stream.concat(Stream.of(Wrapper.this.container), + StreamSupport.stream(Wrapper.this.container.descend().spliterator(), false))); + } + + return memberStream; + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IClass.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IClass.java new file mode 100644 index 000000000..415bc598e --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IClass.java @@ -0,0 +1,61 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.visitation.ITransformer; +import dummy.visitation.ITransformerWithContext; +import dummy.visitation.IVisitor; +import dummy.visitation.IVisitorWithContext; +import java.lang.Iterable; + +/** + * Represent a general class of the meta-model. + */ +public interface IClass { + /** + * Iterate over all the class instances referenced from this instance + * without further recursion. + */ + Iterable descendOnce(); + + /** + * Iterate recursively over all the class instances referenced from this instance. + */ + Iterable descend(); + + /** + * Accept the {@code visitor} to visit this instance + * for double dispatch. + */ + void accept(IVisitor visitor); + + /** + * Accept the visitor to visit this instance for double dispatch + * with the {@code context}. + */ + void accept( + IVisitorWithContext visitor, + ContextT context); + + /** + * Accept the {@code transformer} to transform this instance + * for double dispatch. + */ + T transform(ITransformer transformer); + + /** + * Accept the {@code transformer} to visit this instance + * for double dispatch with the {@code context}. + */ + T transform( + ITransformerWithContext transformer, + ContextT context); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IContainer.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IContainer.java new file mode 100644 index 000000000..451868e8d --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IContainer.java @@ -0,0 +1,47 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import dummy.types.model.IClass; +import java.util.Optional; + +public interface IContainer extends IClass { + List getItems(); + + void setItems(List items); + + List getSomeItems(); + + void setSomeItems(List someItems); + + Optional> getSpecialItems(); + + void setSpecialItems(List specialItems); + + /** + * Count the {@code items}. + * + *

The arguments of the methods are never generic, so only the sequences of + * {@link dummy.types.model.IItem} can be passed to it, but not the sequences of + * the descendants. + */ + Long countItems(List items); + + /** + * Iterate over specialItems, if set, and otherwise return an empty enumerable. + */ + Iterable overSpecialItemsOrEmpty(); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IItem.java new file mode 100644 index 000000000..80091570b --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IItem.java @@ -0,0 +1,29 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import dummy.types.model.IClass; +import java.util.Optional; + +public interface IItem extends IClass { + String getName(); + + void setName(String name); + + Optional getNickname(); + + void setNickname(String nickname); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISomeItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISomeItem.java new file mode 100644 index 000000000..047e582a6 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISomeItem.java @@ -0,0 +1,22 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import dummy.types.model.IItem; + +public interface ISomeItem extends IItem { + // Intentionally empty. +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISpecialItem.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISpecialItem.java new file mode 100644 index 000000000..62331a3b4 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/ISpecialItem.java @@ -0,0 +1,25 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import dummy.types.model.ISomeItem; +import java.util.Optional; + +public interface ISpecialItem extends ISomeItem { + Long getLevel(); + + void setLevel(Long level); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IWrapper.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IWrapper.java new file mode 100644 index 000000000..3c1e73640 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/types/model/IWrapper.java @@ -0,0 +1,25 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.types.model; + +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import java.util.List; +import dummy.types.model.IClass; +import java.util.Optional; + +public interface IWrapper extends IClass { + IContainer getContainer(); + + void setContainer(IContainer container); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/verification/Verification.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/verification/Verification.java new file mode 100644 index 000000000..d4797ea50 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/verification/Verification.java @@ -0,0 +1,709 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.verification; + +import java.lang.Iterable; +import java.math.BigInteger; +import java.util.Arrays; +import java.util.Collections; +import java.util.function.Consumer; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import dummy.common.*; +import dummy.constants.*; +import dummy.reporting.Reporting; +import dummy.types.enums.*; +import dummy.types.model.*; +import dummy.visitation.AbstractTransformer; +import java.util.List; +import java.util.Optional; + +public class Verification { + /** + * Check that the names of the {@code items} are unique. + * + *

The {@code items} are a generic sequence, so the sequences of + * the descendants of {@link dummy.types.model.IItem} can be passed without a copy. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean namesAreUnique( + List items) { + Set nameSet = new HashSet(); + for (var item : items) { + if (nameSet.contains(item.getName())) { + return false; + } + nameSet.add(item.getName()); + } + return true; + } + + /** + * Iterate over a generic sequence in a generator. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean namesAreNonEmpty( + List items) { + return items.stream().allMatch( + item -> StringHelpers.len(item.getName()) > 0); + } + + /** + * Narrow the items of a generic sequence in a generator. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean someSpecialItemHasLevel( + List items) { + return items.stream().anyMatch( + item -> item instanceof ISpecialItem + && ((ISpecialItem) item).getLevel() > 0); + } + + /** + * Index a generic sequence, and narrow the item. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean firstIsSpecial( + List items) { + if (items.size() == 0) { + return false; + } + var first = items.get(0); + if (first instanceof ISpecialItem) { + return ((ISpecialItem) first).getLevel() > 0; + } + return false; + } + + /** + * Check that the nicknames of the {@code items} are unique, if specified. + * + *

This mirrors the body of {@code ID_shorts_are_unique} in aas-core-meta V3.2: + * a local variable takes an optional property of an item of a generic sequence, + * and the loop continues if it is None. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean nicknamesAreUnique( + List items) { + Set nicknameSet = new HashSet(); + for (var item : items) { + var nickname = item.getNickname(); + if (!nickname.isPresent()) { + continue; + } + if (nicknameSet.contains(nickname.get())) { + return false; + } + nicknameSet.add(nickname.get()); + } + return true; + } + + /** + * Narrow the loop variable over a generic sequence in a for-loop. + * + *

The loop variable is of the type parameter in Go, so the down-cast needs to + * go through {@code any(...)}, and of the template parameter in C++. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean levelsOfSpecialItemsArePositive( + List items) { + for (var item : items) { + if (item instanceof ISpecialItem) { + if (((ISpecialItem) item).getLevel() <= 0) { + return false; + } + } + } + return true; + } + + /** + * Narrow an immutable local variable which holds an item of a generic sequence. + * + *

In contrast to {@link dummy.verification.Verification#firstIsSpecial}, the local variable is annotated, so + * it is of the parent class instead of the type parameter. Hence, Go down-casts + * it without {@code any(...)}, and C++ binds the item to a constant reference to + * the shared pointer of the parent class. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean lastIsSpecial( + List items) { + if (items.size() == 0) { + return false; + } + final IItem last = items.get(items.size() - 1); + if (last instanceof ISpecialItem) { + return ((ISpecialItem) last).getLevel() > 0; + } + return false; + } + + /** + * Mix two generic sequences with an argument which is not generic. + * + *

The names must differ, except for the {@code sharedName}, which may repeat. + * + *

Go declares two type parameters, and C++ two template parameters. The nested + * generators iterate over two different generic sequences, so their variables + * are of two different type parameters. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + * + *

The argument {@code otherItems} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link ISomeItem} can be passed without a copy. + */ + public static Boolean namesAreDisjoint( + List items, + List otherItems, + String sharedName) { + return items.stream().allMatch( + item -> otherItems.stream().allMatch( + otherItem -> !Objects.equals(item.getName(), otherItem.getName()) + || Objects.equals(item.getName(), sharedName))); + } + + /** + * Use the immutable aliases of the generic sequences. + * + *

An immutable alias of the very same type shares the type annotation of + * the aliased generic sequence, so it is a generic sequence itself. Go declares + * the alias with the type parameter, C++ with the template parameter, while C# + * and Java infer its type with {@code var}. The {@code alias_of_alias} aliases an alias, + * and the {@code optional_alias} aliases an optional generic sequence. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + * + *

The argument {@code optionalItems} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean aliasedNamesAreUnique( + List items, + Optional> optionalItems) { + final var alias = items; + final var aliasOfAlias = alias; + final var optionalAlias = optionalItems; + for (var item : alias) { + if (item instanceof ISpecialItem) { + if (((ISpecialItem) item).getLevel() <= 0) { + return false; + } + } + } + return ( + aliasOfAlias.stream().allMatch( + item -> StringHelpers.len(item.getName()) > 0) + ) + && namesAreUnique(aliasOfAlias) + && optionalNamesAreUnique(optionalAlias) + && ( + (!optionalAlias.isPresent()) + || namesAreUnique(optionalAlias.orElse(null)) + ); + } + + /** + * Look up an instance of a descendant in a generic sequence. + * + *

Go compares the instances as {@code any}'s, as the items are of the type parameter, + * and C++ converts the member to the shared pointer of {@link dummy.types.model.IItem} so that + * the raw pointers are comparable for any template parameter. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean containsSomeItem( + ISomeItem someItem, + List items) { + return items.contains(someItem); + } + + /** + * Look up the items of a generic sequence in another generic sequence. + * + *

The member and the items are of two different type parameters. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + * + *

The argument {@code otherItems} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link ISomeItem} can be passed without a copy. + */ + public static Boolean itemsOverlap( + List items, + List otherItems) { + return otherItems.stream().anyMatch( + otherItem -> items.contains(otherItem)); + } + + /** + * Look up an instance in a sequence which is not generic. + * + *

The {@link dummy.types.impl.SpecialItem} has no descendants, so the {@code items} are not + * a generic sequence, but Go still compares the instances as {@code any}'s. + */ + public static Boolean containsSpecialItem( + ISpecialItem specialItem, + List items) { + return items.contains(specialItem); + } + + /** + * Pass the local variables on to generic sequences. + * + *

The {@code some_items} is a read-only alias without an annotation, while + * the {@code all_some_items} is an immutable local variable declared with + * {@code Final[...]}. Both hold the sequences of {@link dummy.types.impl.SomeItem}, and are passed + * on to the generic sequences of {@link dummy.types.model.IItem}, required as well as optional. + */ + public static Boolean containerNamesAreUnique( + IContainer container) { + var someItems = container.getSomeItems(); + final List allSomeItems = container.getSomeItems(); + return namesAreUnique(someItems) + && namesAreUnique(allSomeItems) + && optionalNamesAreUnique(Optional.of(allSomeItems)); + } + + /** + * Pass an optional generic sequence on to a generic sequence. + * + *

The argument {@code items} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link IItem} can be passed without a copy. + */ + public static Boolean optionalNamesAreUnique( + Optional> items) { + return (!items.isPresent()) + || namesAreUnique(items.orElse(null)); + } + + /** + * Pass the generic sequences of the descendants on. + * + *

The {@code someItems} are passed on as a required and as an optional + * sequence of the parent class, while the {@code specialItems} are passed on + * as an optional sequence and, once narrowed, as a required sequence of the parent + * class of the parent class. + * + *

The argument {@code someItems} is declared with the wildcard {@code List} instead of the invariant {@code List} so that the lists of the descendants of {@link ISomeItem} can be passed without a copy. + */ + public static Boolean someNamesAreUnique( + List someItems, + Optional> specialItems) { + return namesAreUnique(someItems) + && optionalNamesAreUnique(Optional.of(someItems)) + && optionalNamesAreUnique(specialItems) + && optionalNamesAreUnique(Optional.empty()) + && ( + (!specialItems.isPresent()) + || namesAreUnique(specialItems.orElse(null)) + ); + } + + /** + * Check that the levels are positive. + * + *

The {@link dummy.types.impl.SpecialItem} has no descendants, so the {@code items} are not + * a generic sequence. + */ + public static Boolean levelsArePositive( + List items) { + return items.stream().allMatch( + item -> item.getLevel() > 0); + } + + /** + * Hash allowed enum values for efficient validation of enums. + */ + private static class _EnumValueSet { + + } + + private static final _Transformer transformer = new _Transformer(); + + private static class _Transformer extends AbstractTransformer> { + @Override + public Stream transformSomeItem( + ISomeItem that) { + Stream errorStream = Stream.empty(); + + // No verification has been defined for SomeItem. + + return errorStream; + } + + @Override + public Stream transformSpecialItem( + ISpecialItem that) { + Stream errorStream = Stream.empty(); + + // No verification has been defined for SpecialItem. + + return errorStream; + } + + @Override + public Stream transformContainer( + IContainer that) { + Stream errorStream = Stream.empty(); + + if (!( + !(that.getSpecialItems().isPresent()) + || ( + that.getSpecialItems().get().stream().allMatch( + specialItem -> containsSpecialItem(specialItem, that.getSpecialItems().orElse(null))) + ))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Each special item must be among the special items."))); + } + + if (!( + that.getSomeItems().size() == 0 + || itemsOverlap(that.getItems(), that.getSomeItems()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "If there are some items, they must overlap with the items."))); + } + + if (!( + that.getSomeItems().stream().allMatch( + someItem -> containsSomeItem(someItem, that.getItems())))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Some items must be among the items."))); + } + + if (!( + aliasedNamesAreUnique(that.getSomeItems(), that.getSpecialItems()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of some items and of special items must be unique " + + "through aliases."))); + } + + if (!( + namesAreDisjoint(that.getItems(), that.getSomeItems(), "x"))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of the items and of some items must be disjoint."))); + } + + if (!( + !someSpecialItemHasLevel(that.getItems()) + || lastIsSpecial(that.getItems()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "If there is a special item with level, the last item must " + + "be special."))); + } + + if (!levelsOfSpecialItemsArePositive(that.getItems())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Levels of the special items among the items must be " + + "positive."))); + } + + if (!nicknamesAreUnique(that.getSomeItems())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Nicknames of some items must be unique."))); + } + + if (!( + that.countItems(that.getItems()) == that.getItems().size())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Count of the items must match the number of the items."))); + } + + if (!optionalNamesAreUnique(that.getSpecialItems())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of the special items must be unique."))); + } + + if (!( + !(that.getSpecialItems().isPresent()) + || firstIsSpecial(that.getSpecialItems().orElse(null)))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "The first special item must be special."))); + } + + if (!( + !(that.getSpecialItems().isPresent()) + || levelsArePositive(that.getSpecialItems().orElse(null)))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Levels of the special items must be positive."))); + } + + if (!( + !someSpecialItemHasLevel(that.getItems()) + || firstIsSpecial(that.getItems()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "If there is a special item with level, the first item must " + + "be special."))); + } + + if (!namesAreNonEmpty(that.getSomeItems())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of some items must be non-empty."))); + } + + if (!( + someNamesAreUnique(that.getSomeItems(), that.getSpecialItems()))) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of some items and of special items must be unique."))); + } + + if (!namesAreUnique(that.getItems())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of the items must be unique."))); + } + + errorStream = Stream.concat(errorStream, + Stream.of(that.getItems()) + .flatMap(Verification::verifyListOf_IItem) + .map(error -> { + error.prependSegment( + new Reporting.NameSegment("items")); + return error; + })); + + errorStream = Stream.concat(errorStream, + Stream.of(that.getSomeItems()) + .flatMap(Verification::verifyListOf_ISomeItem) + .map(error -> { + error.prependSegment( + new Reporting.NameSegment("someItems")); + return error; + })); + + if (that.getSpecialItems().isPresent()) { + errorStream = Stream.concat(errorStream, + Stream.of(that.getSpecialItems().get()) + .flatMap(Verification::verifyListOf_ISpecialItem) + .map(error -> { + error.prependSegment( + new Reporting.NameSegment("specialItems")); + return error; + })); + } + + return errorStream; + } + + @Override + public Stream transformWrapper( + IWrapper that) { + Stream errorStream = Stream.empty(); + + if (!containerNamesAreUnique(that.getContainer())) { + errorStream = Stream.concat(errorStream, + Stream.of(new Reporting.Error( + "Invariant violated:\n" + + "Names of some items of the container must be unique."))); + } + + errorStream = Stream.concat(errorStream, + Stream.of(that.getContainer()) + .flatMap(Verification::verifyToErrorStream) + .map(error -> { + error.prependSegment( + new Reporting.NameSegment("container")); + return error; + })); + + return errorStream; + } + } + + public static Stream verifyToErrorStream(IClass that) { + return transformer.transform(that); + } + + private static class _ValidationErrorIterable implements Iterable { + private final IClass element; + + public _ValidationErrorIterable(IClass element) { + this.element = element; + } + + @Override + public Iterator iterator() { + Stream stream = stream(); + + return stream.iterator(); + } + + @Override + public void forEach(Consumer action) { + Stream stream = stream(); + + stream.forEach(action); + } + + @Override + public Spliterator spliterator() { + Stream stream = stream(); + + return stream.spliterator(); + } + + private Stream stream() { + return Verification.verifyToErrorStream(element); + } + } + + /** + * Verify the constraints of {@code that} recursively. + * + * @param that The instance of the meta-model to be verified + */ + public static Iterable verify(IClass that) { + return new _ValidationErrorIterable(that); + } + + /** + * Verify the items of {@code that} recursively. + */ + private static Stream verifyListOf_IItem( + List that) { + return Verification.zip( + IntStream.iterate(0, i -> i + 1).boxed(), + that.stream()) + .flatMap(itemTuple -> + Verification.verifyToErrorStream(itemTuple.getSecond()) + .map(error -> { + error.prependSegment( + new Reporting.IndexSegment(itemTuple.getFirst())); + return error; + })); + } + + /** + * Verify the items of {@code that} recursively. + */ + private static Stream verifyListOf_ISomeItem( + List that) { + return Verification.zip( + IntStream.iterate(0, i -> i + 1).boxed(), + that.stream()) + .flatMap(itemTuple -> + Verification.verifyToErrorStream(itemTuple.getSecond()) + .map(error -> { + error.prependSegment( + new Reporting.IndexSegment(itemTuple.getFirst())); + return error; + })); + } + + /** + * Verify the items of {@code that} recursively. + */ + private static Stream verifyListOf_ISpecialItem( + List that) { + return Verification.zip( + IntStream.iterate(0, i -> i + 1).boxed(), + that.stream()) + .flatMap(itemTuple -> + Verification.verifyToErrorStream(itemTuple.getSecond()) + .map(error -> { + error.prependSegment( + new Reporting.IndexSegment(itemTuple.getFirst())); + return error; + })); + } + + private static class _Pair { + private final A first; + private final B second; + + public _Pair(A first, B second) { + this.first = first; + this.second = second; + } + + public A getFirst() { + return first; + } + + public B getSecond() { + return second; + } + } + + // Java 8 doesn't provide a split operation out of the box, so we have to ship our own. + // Adapted from: https://stackoverflow.com/a/23529010 + private static Stream<_Pair> zip( + Stream a, + Stream b) { + Spliterator aSplit = Objects.requireNonNull(a).spliterator(); + Spliterator bSplit = Objects.requireNonNull(b).spliterator(); + + int characteristics = aSplit.characteristics() & bSplit.characteristics() & + ~(Spliterator.DISTINCT | Spliterator.SORTED); + + long zipSize = ((characteristics & Spliterator.SIZED) != 0) + ? Math.min(aSplit.getExactSizeIfKnown(), bSplit.getExactSizeIfKnown()) + : -1; + + Iterator aIter = Spliterators.iterator(aSplit); + Iterator bIter = Spliterators.iterator(bSplit); + Iterator<_Pair> cIter = new Iterator<_Pair>() { + @Override + public boolean hasNext() { + return aIter.hasNext() && bIter.hasNext(); + } + + @Override + public _Pair next() { + return new _Pair<>(aIter.next(), bIter.next()); + } + }; + + Spliterator<_Pair> split = Spliterators.spliterator(cIter, zipSize, characteristics); + return StreamSupport.stream(split, false); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformer.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformer.java new file mode 100644 index 000000000..6d16e57b7 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformer.java @@ -0,0 +1,44 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; +import dummy.visitation.ITransformer; + +/** + * Perform double-dispatch to transform recursively + * the instances into something else. + * + * @param type of the transformation result + */ +public abstract class AbstractTransformer implements ITransformer +{ + public T transform(IClass that) + { + return that.transform(this); + } + + public abstract T transformSomeItem( + ISomeItem that + ); + + public abstract T transformSpecialItem( + ISpecialItem that + ); + + public abstract T transformContainer( + IContainer that + ); + + public abstract T transformWrapper( + IWrapper that + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformerWithContext.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformerWithContext.java new file mode 100644 index 000000000..2ac6b3cbe --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractTransformerWithContext.java @@ -0,0 +1,54 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; +import dummy.visitation.ITransformerWithContext; + +/** + * Perform double-dispatch to transform recursively + * the instances into something else. + * + *

When you use the transformer, please always call the main dispatching method + * {@link AbstractTransformerWithContext#transform}. You should most probably never call the {@code transform} + * methods directly. They are only made public so that model classes can access them. + * + * @param structure of the context + * @param type of the transformation result + */ +public abstract class AbstractTransformerWithContext + implements ITransformerWithContext +{ + public T transform(IClass that, ContextT context) + { + return that.transform(this, context); + } + + public abstract T transformSomeItem( + ISomeItem that, + ContextT context + ); + + public abstract T transformSpecialItem( + ISpecialItem that, + ContextT context + ); + + public abstract T transformContainer( + IContainer that, + ContextT context + ); + + public abstract T transformWrapper( + IWrapper that, + ContextT context + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitor.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitor.java new file mode 100644 index 000000000..4ed03c8d3 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitor.java @@ -0,0 +1,37 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; +import dummy.visitation.IVisitor; + +/** + * Perform double-dispatch to visit the concrete instances. + */ +public abstract class AbstractVisitor implements IVisitor +{ + public void visit(IClass that) + { + that.accept(this); + } + public abstract void visitSomeItem( + ISomeItem that + ); + public abstract void visitSpecialItem( + ISpecialItem that + ); + public abstract void visitContainer( + IContainer that + ); + public abstract void visitWrapper( + IWrapper that + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitorWithContext.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitorWithContext.java new file mode 100644 index 000000000..42ec64ec4 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/AbstractVisitorWithContext.java @@ -0,0 +1,45 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; +import dummy.visitation.IVisitorWithContext; + +/** + * Perform double-dispatch to visit the concrete instances + * with context. + * + * @param structure of the context + */ +public abstract class AbstractVisitorWithContext + implements IVisitorWithContext +{ + public void visit(IClass that, ContextT context) + { + that.accept(this, context); + } + public abstract void visitSomeItem( + ISomeItem that, + ContextT context + ); + public abstract void visitSpecialItem( + ISpecialItem that, + ContextT context + ); + public abstract void visitContainer( + IContainer that, + ContextT context + ); + public abstract void visitWrapper( + IWrapper that, + ContextT context + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformer.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformer.java new file mode 100644 index 000000000..302d81071 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformer.java @@ -0,0 +1,39 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; + +/** + * Define the interface for a transformer which transforms recursively + * the instances into something else. + * + *

When you use the transformer, please always call the main dispatching method + * {@link ITransformer#transform}. You should most probably never call the {@code transform} + * methods directly. They are only made public so that model classes can access them. + * + * @param type of the transformation result + */ +public interface ITransformer { + T transform(IClass that); + T transformSomeItem( + ISomeItem that + ); + T transformSpecialItem( + ISpecialItem that + ); + T transformContainer( + IContainer that + ); + T transformWrapper( + IWrapper that + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformerWithContext.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformerWithContext.java new file mode 100644 index 000000000..7c81d9d3f --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/ITransformerWithContext.java @@ -0,0 +1,44 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; + +/** + * Define the interface for a transformer which recursively transforms + * the instances into something else while the context is passed along. + * + *

When you use the transformer, please always call the main dispatching method + * {@link ITransformerWithContext#transform}. You should most probably never call the {@code transform} + * methods directly. They are only made public so that model classes can access them. + * + * @param structure of the context + * @param type of the transformation result + */ +public interface ITransformerWithContext { + T transform(IClass that, ContextT context); + T transformSomeItem( + ISomeItem that, + ContextT context + ); + T transformSpecialItem( + ISpecialItem that, + ContextT context + ); + T transformContainer( + IContainer that, + ContextT context + ); + T transformWrapper( + IWrapper that, + ContextT context + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitor.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitor.java new file mode 100644 index 000000000..bb180070d --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitor.java @@ -0,0 +1,37 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; + +/** + * Define the interface for a visitor which visits the instances of the model. + * + *

When you use the visitor, please always call the main dispatching method + * {@link IVisitor#visit}. You should most probably never call the {@code visit*} + * methods directly. They are only made public so that model classes can access them. + */ +public interface IVisitor +{ + void visit(IClass that); + void visitSomeItem( + ISomeItem that + ); + void visitSpecialItem( + ISpecialItem that + ); + void visitContainer( + IContainer that + ); + void visitWrapper( + IWrapper that + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitorWithContext.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitorWithContext.java new file mode 100644 index 000000000..e8a3d10d7 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/IVisitorWithContext.java @@ -0,0 +1,43 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; + +/** + * Define the interface for a visitor which visits the instances of the model. + * + *

When you use the visitor, please always call the main dispatching method + * {@link IVisitorWithContext#visit}. You should most probably never call the {@code visit} + * methods directly. They are only made public so that model classes can access them. + * + * @param structure of the context + */ +public interface IVisitorWithContext +{ + void visit(IClass that, ContextT context); + void visitSomeItem( + ISomeItem that, + ContextT context + ); + void visitSpecialItem( + ISpecialItem that, + ContextT context + ); + void visitContainer( + IContainer that, + ContextT context + ); + void visitWrapper( + IWrapper that, + ContextT context + ); +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/VisitorThrough.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/VisitorThrough.java new file mode 100644 index 000000000..ec2b7d3e7 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/visitation/VisitorThrough.java @@ -0,0 +1,63 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.visitation; + +import dummy.types.model.*; +import dummy.visitation.IVisitor; + +/** + * Just descend through the instances without any action. + * + *

This class is meaningless for itself. However, it is a good base if you + * want to descend through instances and apply actions only on a subset of + * classes. + */ +public class VisitorThrough implements IVisitor { + public void visit(IClass that) { + that.accept(this); + } + + public void visitSomeItem( + ISomeItem that + ) { + // Just descend through, do nothing with {@code that} + for (IClass something : that.descendOnce()) { + visit(something); + } + } + + public void visitSpecialItem( + ISpecialItem that + ) { + // Just descend through, do nothing with {@code that} + for (IClass something : that.descendOnce()) { + visit(something); + } + } + + public void visitContainer( + IContainer that + ) { + // Just descend through, do nothing with {@code that} + for (IClass something : that.descendOnce()) { + visit(something); + } + } + + public void visitWrapper( + IWrapper that + ) { + // Just descend through, do nothing with {@code that} + for (IClass something : that.descendOnce()) { + visit(something); + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlcommon/XmlCommon.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlcommon/XmlCommon.java new file mode 100644 index 000000000..4bbabc16b --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlcommon/XmlCommon.java @@ -0,0 +1,586 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.xmlcommon; + +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.util.Optional; +import java.util.regex.Pattern; +import dummy.reporting.Reporting; + +/** + * Provide the XML primitives shared by the de/serialization. + * + *

The xmlization reads and writes the elements of our own classes, and + * the XML-RPC de/serialization the elements within a JSON-able value. The + * skipping of what carries no information, the check of the XML namespace + * and the framing of an element are the same for both, and live here. + * + *

Java has no visibility which would show a class to a sibling package + * and hide it from the user of the library, so everything here is public. + */ +public final class XmlCommon { + private XmlCommon() { + // NOTE (mristin): + // The XML primitives are stateless, so there is nothing to + // instantiate. + } + + /** + * The XML namespace in which all the elements of a document reside. + * + *

The elements of the XML-RPC subset, over which a JSON-able value is + * de/serialized, are the one exception: they reside in no namespace at all. + */ + public static final String NAMESPACE = + "https://dummy.com"; + + /** + * Read the content of an element which has already been opened. + * + *

{isEmpty} tells whether that element was self-closing. + */ + @FunctionalInterface + public interface ContentReader { + Reporting.Result read(XMLEventReader reader, boolean isEmpty); + } + + /** + * Read a whole element, opening and closing it. + */ + @FunctionalInterface + public interface ElementReader { + Reporting.Result read(XMLEventReader reader); + } + + /** + * Write {@code that} where {@code writer} already is. + * + *

Every value is written through this one shape, so that the writing + * composes: {@link #writeElement} frames it in a start and an end tag, and + * a class's own {@code write...AsSequence} already is one. + * + *

There is deliberately no second shape for a whole element, as there is + * on the reading side. An element differs from a content only in what it + * writes, never in its shape; the reading needs the distinction because + * a content reader has to be told whether its element was self-closing, and + * a writer has nothing to be told. + * + *

Use sites take a {@code ContentWriter} -- Java's spelling + * of the contravariance -- so that the single writer of an {@link IClass} + * serves wherever the writer of a more specific interface is expected. + */ + @FunctionalInterface + public interface ContentWriter { + void write(T that, XMLStreamWriter writer) throws XMLStreamException; + } + + /** + * Match a run of the four characters which XML calls whitespace. + */ + public static final Pattern WHITESPACE_RUN = Pattern.compile("[ \t\n\r]+"); + + /** + * Read the text of the element which the caller has opened. + * + *

The comments in between are skipped, and the pieces of the text are + * joined, as a reader is free to split it. + */ + public static String readContentAsString( + XMLEventReader reader) throws XMLStreamException { + final StringBuilder content = new StringBuilder(); + + while (reader.peek().isCharacters() + || reader.peek().getEventType() == XMLStreamConstants.COMMENT) { + if (reader.peek().isCharacters()) { + content.append(reader.peek().asCharacters().getData()); + } + reader.nextEvent(); + } + + return content.toString(); + } + + /** + * Normalize {@code text} the way {@code whiteSpace="collapse"} + * prescribes. + * + *

Every atomic XSD type except a string fixes {@code whiteSpace} to + * {@code collapse}, and a schema author can not change it. A tab, a line + * feed and a carriage return each become a space, a run of spaces becomes + * one space, and the leading and trailing spaces go. + * + *

See: https://www.w3.org/TR/xmlschema-2/#rf-whiteSpace + */ + public static String collapseWhitespace(String text) { + return WHITESPACE_RUN.matcher(text).replaceAll(" ").trim(); + } + + /** + * Represent a critical error during the deserialization. + */ + @SuppressWarnings("serial") + public static class DeserializeException extends RuntimeException { + private final String path; + private final String reason; + + public DeserializeException(String path, String reason) { + super(reason + " at: " + ("".equals(path) ? "the beginning" : path)); + this.path = path; + this.reason = reason; + } + + public Optional getPath() { + return Optional.ofNullable(path); + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + } + + /** + * Represent a critical error during the serialization. + */ + @SuppressWarnings("serial") + public static class SerializeException extends RuntimeException { + private final String path; + private final String reason; + + public SerializeException(String path, String reason) { + super(reason + " at: " + ("".equals(path) ? "the beginning" : path)); + this.path = path; + this.reason = reason; + } + + public Optional getPath() { + return Optional.ofNullable(path); + } + + public Optional getReason() { + return Optional.ofNullable(reason); + } + } + + /** + * Signal a failure of the serialization, carrying the path to the culprit. + * + *

The path is built as the stack unwinds -- every container prepends + * the one segment it knows, the property its name and the list the index + * of the item -- which is why this can not be the exception which + * the caller sees already: that one renders its message in its + * constructor, so its path has to be complete by then. The enclosing + * serializer renders and converts. + * + *

This is thrown across a package boundary, from the XML-RPC writers + * into the xmlization's, so it is public. + */ + @SuppressWarnings("serial") + public static class SerializeFailure extends RuntimeException { + private final Reporting.Error error; + + public SerializeFailure(Reporting.Error error) { + super(error.getCause()); + this.error = error; + } + + public Reporting.Error getError() { + return error; + } + } + + public static XMLEvent currentEvent(XMLEventReader reader) { + try { + return reader.peek(); + } catch (XMLStreamException xmlStreamException) { + throw new DeserializeException("", + "Failed in method peek because of: " + + xmlStreamException.getMessage()); + } + } + + public static String getEventTypeAsString(XMLEvent event) { + switch (event.getEventType()) { + case XMLStreamConstants.START_ELEMENT: + return "Start-Element"; + case XMLStreamConstants.END_ELEMENT: + return "End-Element"; + case XMLStreamConstants.PROCESSING_INSTRUCTION: + return "Processing-Instruction"; + case XMLStreamConstants.CHARACTERS: + return "Characters"; + case XMLStreamConstants.COMMENT: + return "Comment"; + case XMLStreamConstants.SPACE: + return "Space"; + case XMLStreamConstants.START_DOCUMENT: + return "Start-Document"; + case XMLStreamConstants.END_DOCUMENT: + return "End-Document"; + case XMLStreamConstants.ENTITY_REFERENCE: + return "Entity-Reference"; + case XMLStreamConstants.ATTRIBUTE: + return "Attribute"; + case XMLStreamConstants.NOTATION_DECLARATION: + return "Notation-Declaration"; + default: + return "Unknown-Type"; + } + } + + public static void skipWhitespaceAndComments(XMLEventReader reader) { + while (whiteSpaceOrComment(reader)) { + reader.next(); + } + } + + public static boolean whiteSpaceOrComment(XMLEventReader reader) { + final XMLEvent currentEvent = currentEvent(reader); + final boolean isComment = (currentEvent != null && + currentEvent.getEventType() == XMLStreamConstants.COMMENT); + final boolean isWhiteSpace = (currentEvent != null && + currentEvent.getEventType() == XMLStreamConstants.CHARACTERS && + currentEvent.asCharacters().isWhiteSpace()); + return isComment || isWhiteSpace; + } + + public static boolean isEmptyElement(XMLEventReader reader) { + // Skip the element node and go to the content + try { + reader.nextEvent(); + } catch (XMLStreamException xmlStreamException) { + throw new DeserializeException("", + "Failed in method isEmptyElement because of: " + + xmlStreamException.getMessage()); + } + return currentEvent(reader).isEndElement(); + } + + /** + * Write {@code that.toString()} as XML content. + * + *

This is the {@link ContentWriter} of every {@code boolean}/ + * {@code long}/{@code double}/{@code String}-typed value, be it + * a property, a list item or a tuple item. + */ + public static void writeStringifiedContent( + T that, + XMLStreamWriter writer) throws XMLStreamException { + writer.writeCharacters(that.toString()); + } + + /** + * Write {@code that} as XML content in the lexical form of + * {@code xs:double}. + * + *

This is the {@link ContentWriter} of every {@code double}-typed + * value, be it a property, a list item or a tuple item. A double can not + * share {@link #writeStringifiedContent} with the other primitives: + * {@code Double.toString} renders an infinity as {@code Infinity}, where + * {@code xs:double} spells it {@code INF}. Only the two infinities differ + * -- {@code NaN} is spelled the same way in both, and a finite number is + * rendered by {@code Double.toString} in a form which {@code xs:double} + * accepts. + * + *

See: https://www.w3.org/TR/xmlschema-2/#double + */ + public static void writeDoubleContent( + Double that, + XMLStreamWriter writer) throws XMLStreamException { + final String text; + if (that.isInfinite()) { + text = (that > 0) ? "INF" : "-INF"; + } else { + text = that.toString(); + } + + writer.writeCharacters(text); + } + + /** + * Describe {@code ns} for an error message. + */ + private static String describeNamespace(String ns) { + return ns.isEmpty() ? "no namespace" : "the namespace " + ns; + } + + private static boolean invalidNameSpace(XMLEvent event, String ns) { + if (event.isStartElement()) { + return !ns.equals(event.asStartElement().getName().getNamespaceURI()); + } else { + return !ns.equals(event.asEndElement().getName().getNamespaceURI()); + } + } + + /** + * Check the namespace and extract the element's name. + */ + private static Reporting.Result tryElementName( + XMLEventReader reader, String ns) { + final XMLEvent currentEvent = currentEvent(reader); + final boolean precondition = currentEvent.isStartElement() || currentEvent.isEndElement(); + if (!precondition) { + throw new IllegalStateException("Expected to be at a start or an end element " + + "but got: " + getEventTypeAsString(currentEvent)); + } + + if (invalidNameSpace(currentEvent, ns)) { + final String namespace = currentEvent.isStartElement() + ? currentEvent.asStartElement().getName().getNamespaceURI() + : currentEvent.asEndElement().getName().getNamespaceURI(); + final Reporting.Error error = new Reporting.Error( + "Expected an element within " + describeNamespace(ns) + + ", but got an element within " + describeNamespace(namespace)); + return Reporting.Result.failure(error); + } + return Reporting.Result.success(currentEvent.isStartElement() + ? currentEvent.asStartElement().getName().getLocalPart() + : currentEvent.asEndElement().getName().getLocalPart()); + } + + /** + * Look up the name of the element which {@code reader} is positioned at. + * + *

This is the single primitive answering "we are at an element, and this is + * its name": {@link #readNamedElement} checks that name against the one its + * container supplied, a dispatcher switches on it, and a property loop uses it + * to select the property. Nothing is consumed, which is what lets a dispatcher + * hand the whole element on to the reader it selected. + */ + private static Reporting.Result peekElementNameInNamespace( + XMLEventReader reader, String ns) { + skipWhitespaceAndComments(reader); + + final XMLEvent currentEvent = currentEvent(reader); + if (currentEvent.isEndDocument()) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML element, but reached the end-of-file")); + } + + if (!currentEvent.isStartElement()) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML element, but got the node of type " + + getEventTypeAsString(currentEvent) + " with the value " + currentEvent)); + } + + return tryElementName(reader, ns); + } + + /** + * Consume the end tag concluding the element called {@code elementName}. + */ + private static Reporting.Result consumeEndElementInNamespace( + XMLEventReader reader, String elementName, String ns) { + skipWhitespaceAndComments(reader); + + final XMLEvent currentEvent = currentEvent(reader); + if (currentEvent.isEndDocument()) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML end element to conclude the element " + elementName + + ", but got the end-of-file")); + } + + if (!currentEvent.isEndElement()) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML end element to conclude the element " + elementName + + ", but got the node of type " + getEventTypeAsString(currentEvent) + + " with the value " + currentEvent)); + } + + final Reporting.Result tryEndElementName = tryElementName(reader, ns); + if (tryEndElementName.isError()) { + return tryEndElementName.castTo(XMLEvent.class); + } + + if (!elementName.equals(tryEndElementName.getResult())) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML end element to conclude the element " + elementName + + ", but got the end element with the name " + tryEndElementName.getResult())); + } + + try { + return Reporting.Result.success(reader.nextEvent()); + } catch (XMLStreamException xmlStreamException) { + throw new DeserializeException("", + "Failed in method consumeEndElement because of: " + + xmlStreamException.getMessage()); + } + } + + /** + * Read a whole element which is expected to be called {@code name} and to + * reside in {@code ns}, and read its content with {@code readContent}. + * + *

The name is data, not a type: an instance reads the XML name of its own + * class, a list item reads {@code "v"} and a tuple item reads {@code "v1"}, + * {@code "v2"}, ... by position. One framer therefore serves them all. + */ + private static Reporting.Result readNamedElementInNamespace( + XMLEventReader reader, String name, String ns, + ContentReader readContent) { + final Reporting.Result tryElementName = peekElementNameInNamespace(reader, ns); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + if (!name.equals(tryElementName.getResult())) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML element " + name + ", but got an XML element " + + tryElementName.getResult())); + } + + final boolean isEmpty = isEmptyElement(reader); + + final Reporting.Result result = + readContent.read(reader, isEmpty); + if (result.isError()) { + return result; + } + + final Reporting.Result endResult = + consumeEndElementInNamespace(reader, name, ns); + if (endResult.isError()) { + return Reporting.Result.failure(endResult.getError()); + } + + return result; + } + + /** + * Look up the name of the element in {@link #NAMESPACE} which + * {@code reader} is positioned at. + * + *

This is the single primitive answering "we are at an element, and this is + * its name": {@link #readNamedElement} checks that name against the one its + * container supplied, a dispatcher switches on it, and a property loop uses it + * to select the property. Nothing is consumed, which is what lets a dispatcher + * hand the whole element on to the reader it selected. + */ + public static Reporting.Result peekElementName(XMLEventReader reader) { + return peekElementNameInNamespace(reader, NAMESPACE); + } + + /** + * Consume the end tag concluding the element called {@code elementName} in + * {@link #NAMESPACE}. + */ + public static Reporting.Result consumeEndElement( + XMLEventReader reader, String elementName) { + return consumeEndElementInNamespace(reader, elementName, NAMESPACE); + } + + /** + * Read a whole element in {@link #NAMESPACE} which is expected to be called + * {@code name}, and read its content with {@code readContent}. + */ + public static Reporting.Result readNamedElement( + XMLEventReader reader, String name, ContentReader readContent) { + return readNamedElementInNamespace(reader, name, NAMESPACE, readContent); + } + + /** + * Read a self-describing element as the content of the property element which + * {@code reader} is already positioned inside. + * + *

This looks like a needless layer over {@code read...FromElement}: the + * reader sits at the very same position in both cases, just before a start + * element, whether that element is the only child of a property element or + * the next item of a list. The layer exists for the error path alone. + * + *

A property wraps its instance in an element of its own, so the failing + * node is one step deeper than the property and the discriminator's name has + * to be prepended: {@code value/property/idShort}. A list item is not + * wrapped -- the item element *is* the indexed child -- so prepending the name + * there would give {@code annotations/*[1]/property/idShort}, which walks one + * level past the element {@code *[1]} already selects and resolves to + * nothing. + * + *

The two callers therefore need different segments, which is why the name + * can not be prepended inside {@code read...FromElement}. Unifying them would + * take a segment carrying a name *and* a position + * ({@code annotations/property[1]}), and that is a change to + * {@link Reporting}, which the verification and the JSON de-serialization + * share. + */ + public static Reporting.Result readNestedElement( + XMLEventReader reader, boolean isEmpty, ElementReader readInner) { + if (isEmpty) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML element representing an instance, " + + "but encountered a self-closing element")); + } + + final Reporting.Result tryElementName = peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + final Reporting.Result result = readInner.read(reader); + if (result.isError()) { + result.getError() + .prependSegment( + new Reporting.NameSegment( + tryElementName.getResult())); + } + + return result; + } + + /** + * Write {@code that} as an XML element named {@code name}, its content + * written by {@code writeContent}. + * + *

An element is nothing but a start and an end tag around a content, so + * there is no writer per property kind: only the content writer differs, + * and the type of the value alone decides which one it is. + * + *

{@code withNamespace} declares {@link #NAMESPACE} on the element, + * which only the outermost element does. + */ + public static void writeElement( + String name, + T that, + XMLStreamWriter writer, + boolean withNamespace, + ContentWriter writeContent) { + try { + writer.writeStartElement(name); + if (withNamespace) { + writer.writeNamespace("xmlns", NAMESPACE); + } + writeContent.write(that, writer); + writer.writeEndElement(); + } catch (XMLStreamException exception) { + throw new SerializeFailure( + new Reporting.Error(exception.getMessage())); + } + } + + /** + * Write {@code that} as an XML element named {@code name} nested in + * another element, so that the XML namespace is not re-declared. + * + *

This is what an item of a list or of a tuple is written with. It + * contributes no segment to the error path: its container has already + * contributed the item's index, and the index selects this very element + * (see {@link #writeListOf} in the generated writers). + */ + public static void writeElement( + String name, + T that, + XMLStreamWriter writer, + ContentWriter writeContent) { + writeElement(name, that, writer, false, writeContent); + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlization/Xmlization.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlization/Xmlization.java new file mode 100644 index 000000000..cbbf6c23e --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/main/java/dummy/xmlization/Xmlization.java @@ -0,0 +1,1255 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.xmlization; + +import javax.xml.stream.events.XMLEvent; +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamWriter; +import java.util.ArrayList; +import java.util.Base64; +import java.util.function.Function; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import dummy.common.*; +import dummy.reporting.Reporting; +import dummy.stringification.Stringification; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import dummy.visitation.*; +import dummy.xmlcommon.XmlCommon; + +/** + * Provide de/serialization of meta-model classes to/from XML. + */ +public class Xmlization { + /** + * The XML namespace of the meta-model + */ + public static final String NAMESPACE = + XmlCommon.NAMESPACE; + + + /** + * Implement the deserialization of meta-model classes from XML. + * + *

The implementation propagates an {@link Reporting.Error} instead of + * relying on exceptions. Under the assumption that incorrect data is much less + * frequent than correct data, this makes the deserialization more + * efficient. + * + *

However, we do not want to force the client to deal with + * the {@link Reporting.Error} class as this is not intuitive. + * Therefore we distinguish the implementation, realized in + * {@link _DeserializeImplementation}, and the facade given in + * {@link Deserialize} class. + */ + private static class _DeserializeImplementation + { + private static void skipStartDocument(XMLEventReader reader){ + if (XmlCommon.currentEvent(reader).isStartDocument()){ + reader.next(); + } + } + + /** + * Convert the text content of an element which has already been opened. + */ + @FunctionalInterface + private interface ContentConverter { + T convert(XMLEventReader reader) throws XMLStreamException; + } + + private static Long readContentAsLong(XMLEventReader reader) throws XMLStreamException { + final StringBuilder content = new StringBuilder(); + + while (reader.peek().isCharacters() || reader.peek().getEventType() == XMLStreamConstants.COMMENT) { + if (reader.peek().isCharacters()) { + content.append(reader.peek().asCharacters().getData()); + } + reader.nextEvent(); + } + + return Long.valueOf(XmlCommon.collapseWhitespace(content.toString())); + } + + /** + * Read the text content of an element and convert it with + * {@code convert}. + * + *

A self-closing element is an error, since there is no text to convert. + */ + private static Reporting.Result readText( + XMLEventReader reader, + boolean isEmpty, + ContentConverter convert, + String typeName) { + if (isEmpty) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML content representing " + typeName + + ", but encountered a self-closing element")); + } + + if (XmlCommon.currentEvent(reader).isEndDocument()) { + return Reporting.Result.failure(new Reporting.Error( + "Expected an XML content representing " + typeName + + ", but reached the end-of-file")); + } + + try { + return Reporting.Result.success(convert.convert(reader)); + } catch (Exception exception) { + return Reporting.Result.failure(new Reporting.Error( + "The XML content could not be de-serialized as " + typeName + ": " + + exception.getMessage())); + } + } + + /** + * Read the text content of an element and convert it with + * {@code convert}, giving {@code whenEmpty} for a self-closing element. + */ + private static Reporting.Result readText( + XMLEventReader reader, + boolean isEmpty, + ContentConverter convert, + String typeName, + T whenEmpty) { + if (isEmpty) { + return Reporting.Result.success(whenEmpty); + } + + return readText(reader, isEmpty, convert, typeName); + } + + /** + * Read the items of a list, each with {@code readItem}. + * + *

Every start element is considered to mark the start of an item. Reading + * stops as soon as a non-start element is encountered. + */ + private static Reporting.Result> readList( + XMLEventReader reader, boolean isEmpty, XmlCommon.ElementReader readItem) { + final List result = new ArrayList<>(); + if (isEmpty) { + return Reporting.Result.success(result); + } + + XmlCommon.skipWhitespaceAndComments(reader); + int index = 0; + if (!XmlCommon.currentEvent(reader).isStartElement()) { + final Reporting.Error error = new Reporting.Error( + "Expected a start element opening an item of the list, " + + "but got an XML " + XmlCommon.getEventTypeAsString(XmlCommon.currentEvent(reader))); + return Reporting.Result.failure(error); + } + + while (XmlCommon.currentEvent(reader).isStartElement()) { + final Reporting.Result itemResult = readItem.read(reader); + if (itemResult.isError()) { + itemResult.getError() + .prependSegment( + new Reporting.IndexSegment(index)); + return Reporting.Result.failure(itemResult.getError()); + } + + result.add(itemResult.getResult()); + index++; + XmlCommon.skipWhitespaceAndComments(reader); + } + + return Reporting.Result.success(result); + } + + /** + * Check whether the sequence of the properties has ended. + * + *

Only the end tag of the enclosing element concludes a sequence. Reaching + * the end-of-file does not -- that is an error, which + * {@link #peekElementName} reports when the caller goes on to read the next + * property. + */ + private static boolean atEndOfSequence(XMLEventReader reader) { + XmlCommon.skipWhitespaceAndComments(reader); + return XmlCommon.currentEvent(reader).isEndElement(); + } + + /** + * Report an element which is not a property of the class {@code className}. + */ + private static Reporting.Result unexpectedProperty( + String className, String elementName) { + return Reporting.Result.failure(new Reporting.Error( + "We expected properties of the class " + className + ", " + + "but got an unexpected element " + + "with the name " + elementName)); + } + + /** + * Report a property which the sequence of the properties gave more than once. + */ + private static Reporting.Error duplicatePropertyError(String elementName) { + return new Reporting.Error( + "Property " + elementName + " occurred more than once"); + } + + /** + * Report a required property of the class {@code className} which the + * sequence of the properties did not give. + */ + private static Reporting.Result missingRequiredProperty( + String propertyName, String className) { + return Reporting.Result.failure(new Reporting.Error( + "The required property " + propertyName + " has not been given " + + "in the XML representation of an instance of class " + className)); + } + + private static Reporting.Result readTextAs_string( + XMLEventReader reader, boolean isEmpty) { + return readText( + reader, + isEmpty, + XmlCommon::readContentAsString, + "String", + ""); + } + + private static Reporting.Result readTextAs_long( + XMLEventReader reader, boolean isEmpty) { + return readText( + reader, + isEmpty, + _DeserializeImplementation::readContentAsLong, + "Long"); + } + + private static Reporting.Result> readListOf_IItem( + XMLEventReader reader, boolean isEmpty) { + return readList( + reader, isEmpty, _DeserializeImplementation::readIItemFromElement); + } + + private static Reporting.Result> readListOf_ISomeItem( + XMLEventReader reader, boolean isEmpty) { + return readList( + reader, isEmpty, _DeserializeImplementation::readISomeItemFromElement); + } + + private static Reporting.Result> readListOf_ISpecialItem( + XMLEventReader reader, boolean isEmpty) { + return readList( + reader, isEmpty, _DeserializeImplementation::readSpecialItemFromElement); + } + + /** + * Deserialize an instance of IItem from an XML element. + */ + private static Reporting.Result readIItemFromElement( + XMLEventReader reader) { + // NOTE (mristin): + // We only peek the name, so that the whole element can be handed on to + // the reader which we select below. + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + switch (tryElementName.getResult()) { + case "someItem": + return readSomeItemFromElement(reader); + case "specialItem": + return readSpecialItemFromElement(reader); + default: + return Reporting.Result.failure(new Reporting.Error( + "Unexpected element with the name " + tryElementName.getResult())); + } + } + + /** + * Deserialize an instance of class SomeItem from a sequence of XML elements. + * + *

If {@code isEmptySequence} is set, we should try to deserialize + * the instance from an empty sequence. That is, the parent element + * was a self-closing element. + */ + private static Reporting.Result readSomeItemFromSequence( + XMLEventReader reader, + boolean isEmptySequence) { + String theName = null; + String theNickname = null; + + if (!isEmptySequence) { + while (!atEndOfSequence(reader)) { + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + final String elementName = tryElementName.getResult(); + final boolean isEmptyProperty = XmlCommon.isEmptyElement(reader); + + Reporting.Error valueError = null; + + switch (elementName) { + case "name": { + if (theName != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readTextAs_string(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theName = value.getResult(); + } + break; + } + case "nickname": { + if (theNickname != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readTextAs_string(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theNickname = value.getResult(); + } + break; + } + default: + return unexpectedProperty("SomeItem", elementName); + } + + if (valueError != null) { + valueError.prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(valueError); + } + + final Reporting.Result endResult = XmlCommon.consumeEndElement(reader, elementName); + if (endResult.isError()) { + // NOTE (mristin): + // Whatever unexpected node precedes the end element still lies within + // the property. + endResult.getError().prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(endResult.getError()); + } + } + } + + if (theName == null) { + return missingRequiredProperty("name", "SomeItem"); + } + + return Reporting.Result.success(new SomeItem( + theName, + theNickname)); + } + + /** + * Deserialize an instance of ISomeItem from an XML element. + */ + private static Reporting.Result readISomeItemFromElement( + XMLEventReader reader) { + // NOTE (mristin): + // We only peek the name, so that the whole element can be handed on to + // the reader which we select below. + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + switch (tryElementName.getResult()) { + case "specialItem": + return readSpecialItemFromElement(reader); + case "someItem": + return readSomeItemFromElement(reader); + default: + return Reporting.Result.failure(new Reporting.Error( + "Unexpected element with the name " + tryElementName.getResult())); + } + } + + /** + * Deserialize an instance of class SomeItem from an XML element. + */ + private static Reporting.Result readSomeItemFromElement( + XMLEventReader reader) { + return XmlCommon.readNamedElement( + reader, + "someItem", + _DeserializeImplementation::readSomeItemFromSequence); + } + + /** + * Deserialize an instance of class SpecialItem from a sequence of XML elements. + * + *

If {@code isEmptySequence} is set, we should try to deserialize + * the instance from an empty sequence. That is, the parent element + * was a self-closing element. + */ + private static Reporting.Result readSpecialItemFromSequence( + XMLEventReader reader, + boolean isEmptySequence) { + String theName = null; + String theNickname = null; + Long theLevel = null; + + if (!isEmptySequence) { + while (!atEndOfSequence(reader)) { + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + final String elementName = tryElementName.getResult(); + final boolean isEmptyProperty = XmlCommon.isEmptyElement(reader); + + Reporting.Error valueError = null; + + switch (elementName) { + case "name": { + if (theName != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readTextAs_string(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theName = value.getResult(); + } + break; + } + case "nickname": { + if (theNickname != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readTextAs_string(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theNickname = value.getResult(); + } + break; + } + case "level": { + if (theLevel != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readTextAs_long(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theLevel = value.getResult(); + } + break; + } + default: + return unexpectedProperty("SpecialItem", elementName); + } + + if (valueError != null) { + valueError.prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(valueError); + } + + final Reporting.Result endResult = XmlCommon.consumeEndElement(reader, elementName); + if (endResult.isError()) { + // NOTE (mristin): + // Whatever unexpected node precedes the end element still lies within + // the property. + endResult.getError().prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(endResult.getError()); + } + } + } + + if (theName == null) { + return missingRequiredProperty("name", "SpecialItem"); + } + + if (theLevel == null) { + return missingRequiredProperty("level", "SpecialItem"); + } + + return Reporting.Result.success(new SpecialItem( + theName, + theLevel, + theNickname)); + } + + /** + * Deserialize an instance of class SpecialItem from an XML element. + */ + private static Reporting.Result readSpecialItemFromElement( + XMLEventReader reader) { + return XmlCommon.readNamedElement( + reader, + "specialItem", + _DeserializeImplementation::readSpecialItemFromSequence); + } + + /** + * Deserialize an instance of class Container from a sequence of XML elements. + * + *

If {@code isEmptySequence} is set, we should try to deserialize + * the instance from an empty sequence. That is, the parent element + * was a self-closing element. + */ + private static Reporting.Result readContainerFromSequence( + XMLEventReader reader, + boolean isEmptySequence) { + List theItems = null; + List theSomeItems = null; + List theSpecialItems = null; + + if (!isEmptySequence) { + while (!atEndOfSequence(reader)) { + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + final String elementName = tryElementName.getResult(); + final boolean isEmptyProperty = XmlCommon.isEmptyElement(reader); + + Reporting.Error valueError = null; + + switch (elementName) { + case "items": { + if (theItems != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result> value = + readListOf_IItem(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theItems = value.getResult(); + } + break; + } + case "someItems": { + if (theSomeItems != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result> value = + readListOf_ISomeItem(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theSomeItems = value.getResult(); + } + break; + } + case "specialItems": { + if (theSpecialItems != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result> value = + readListOf_ISpecialItem(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theSpecialItems = value.getResult(); + } + break; + } + default: + return unexpectedProperty("Container", elementName); + } + + if (valueError != null) { + valueError.prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(valueError); + } + + final Reporting.Result endResult = XmlCommon.consumeEndElement(reader, elementName); + if (endResult.isError()) { + // NOTE (mristin): + // Whatever unexpected node precedes the end element still lies within + // the property. + endResult.getError().prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(endResult.getError()); + } + } + } + + if (theItems == null) { + return missingRequiredProperty("items", "Container"); + } + + if (theSomeItems == null) { + return missingRequiredProperty("someItems", "Container"); + } + + return Reporting.Result.success(new Container( + theItems, + theSomeItems, + theSpecialItems)); + } + + /** + * Deserialize an instance of class Container from an XML element. + */ + private static Reporting.Result readContainerFromElement( + XMLEventReader reader) { + return XmlCommon.readNamedElement( + reader, + "container", + _DeserializeImplementation::readContainerFromSequence); + } + + /** + * Deserialize an instance of class Wrapper from a sequence of XML elements. + * + *

If {@code isEmptySequence} is set, we should try to deserialize + * the instance from an empty sequence. That is, the parent element + * was a self-closing element. + */ + private static Reporting.Result readWrapperFromSequence( + XMLEventReader reader, + boolean isEmptySequence) { + IContainer theContainer = null; + + if (!isEmptySequence) { + while (!atEndOfSequence(reader)) { + final Reporting.Result tryElementName = XmlCommon.peekElementName(reader); + if (tryElementName.isError()) { + return Reporting.Result.failure(tryElementName.getError()); + } + + final String elementName = tryElementName.getResult(); + final boolean isEmptyProperty = XmlCommon.isEmptyElement(reader); + + Reporting.Error valueError = null; + + switch (elementName) { + case "container": { + if (theContainer != null) { + valueError = duplicatePropertyError(elementName); + break; + } + + final Reporting.Result value = + readContainerFromSequence(reader, isEmptyProperty); + if (value.isError()) { + valueError = value.getError(); + } else { + theContainer = value.getResult(); + } + break; + } + default: + return unexpectedProperty("Wrapper", elementName); + } + + if (valueError != null) { + valueError.prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(valueError); + } + + final Reporting.Result endResult = XmlCommon.consumeEndElement(reader, elementName); + if (endResult.isError()) { + // NOTE (mristin): + // Whatever unexpected node precedes the end element still lies within + // the property. + endResult.getError().prependSegment( + new Reporting.NameSegment( + elementName)); + return Reporting.Result.failure(endResult.getError()); + } + } + } + + if (theContainer == null) { + return missingRequiredProperty("container", "Wrapper"); + } + + return Reporting.Result.success(new Wrapper( + theContainer)); + } + + /** + * Deserialize an instance of class Wrapper from an XML element. + */ + private static Reporting.Result readWrapperFromElement( + XMLEventReader reader) { + return XmlCommon.readNamedElement( + reader, + "wrapper", + _DeserializeImplementation::readWrapperFromSequence); + } + } + + /** + * Deserialize instances of meta-model classes from XML. + */ + /**

+   * Here is an example how to parse an instance of class IItem:
+   * {@code
+   * XMLEventReader reader = xmlFactory.createXMLEventReader(...some arguments...);
+   * IItem anInstance = Deserialize.deserializeIItem(
+   *   reader);
+   * }
+   * 
+ * + *
+   * If the elements live in a namespace, you have to supply it. For example:
+   * {@code
+   * XMLEventReader reader = xmlFactory.createXMLEventReader(...some arguments...);
+   * IItem anInstance = Deserialize.deserializeIItem(
+   *   reader,
+   *   "http://www.example.com/5/12");
+   * }
+   * 
+ */ + public static class Deserialize + { + /** + * Deserialize an instance of IItem from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static IItem deserializeIItem( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readIItemFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of ISomeItem from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static ISomeItem deserializeISomeItem( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readISomeItemFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of SomeItem from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static SomeItem deserializeSomeItem( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readSomeItemFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of SpecialItem from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static SpecialItem deserializeSpecialItem( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readSpecialItemFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of Container from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static Container deserializeContainer( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readContainerFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + + /** + * Deserialize an instance of Wrapper from {@code reader}. + * + * @param reader Initialized XML reader with reader.peek() set to the element + */ + public static Wrapper deserializeWrapper( + XMLEventReader reader) { + + _DeserializeImplementation.skipStartDocument(reader); + XmlCommon.skipWhitespaceAndComments(reader); + + // NOTE (mristin): + // The path starts at the root element as it is in the document, which + // might well be another element than the expected one. + final String rootElementName = ( + XmlCommon.currentEvent(reader).isStartElement() + ? XmlCommon.currentEvent(reader).asStartElement().getName().getLocalPart() + : null); + + Reporting.Result result = + _DeserializeImplementation.readWrapperFromElement( + reader); + + return result.onError(error -> { + if (rootElementName != null) { + error.prependSegment(new Reporting.NameSegment(rootElementName)); + } + throw new XmlCommon.DeserializeException( + Reporting.generateRelativeXPath(error.getPathSegments()), + error.getCause()); + }); + } + } + + /** + * Serialize recursively the instances as XML elements. + */ + static class _VisitorWithWriter + extends AbstractVisitorWithContext { + + /** + * Declare the XML namespace on the element which this visitor writes. + * + *

Only the outermost element carries the declaration. The obvious + * alternative -- a flag cleared once the first element has been written -- + * would be mutable state, and state forces every method of this class to be + * an instance method, which in turn makes every method reference to it + * capture {@code this} and allocate. Pinning the flag in the constructor + * instead costs one visitor per case, allocated once for the whole program, + * and lets everything else here be {@code static}. + */ + private final boolean withNamespace; + + private _VisitorWithWriter(boolean withNamespace) { + this.withNamespace = withNamespace; + } + + /** + * Write the outermost element, which declares the XML namespace. + */ + private static final _VisitorWithWriter ROOT = + new _VisitorWithWriter(true); + + /** + * Write an element nested in another one, which never re-declares the XML + * namespace. + */ + private static final _VisitorWithWriter NESTED = + new _VisitorWithWriter(false); + + /** + * Write {@code that} as the XML element of a property called + * {@code name}. + * + *

This is {@link #writeElement} plus the one thing a property knows + * which nothing below it does: its own name. Prepending it here, once, + * saves a {@code try} around every one of the property writes. + * + *

The path names the getter, and not the XML element: a serialization + * error is reported on an instance, which the caller holds, and + * not on a document which has not been written yet. + */ + private static void writeProperty( + String name, + String getterName, + T that, + XMLStreamWriter writer, + XmlCommon.ContentWriter writeContent) { + try { + XmlCommon.writeElement(name, that, writer, writeContent); + } catch (XmlCommon.SerializeFailure failure) { + failure.getError().prependSegment( + new Reporting.NameSegment(getterName)); + throw failure; + } + } + + /** + * Write {@code that} as the XML element of a property called + * {@code name} if it has been given, and write nothing at all otherwise. + * + *

The {@link Optional} is taken apart here, once, instead of at every + * optional property: asking it and then unwrapping it at the call site + * would call the getter twice, and every call allocates an + * {@link Optional} of its own. + */ + private static void writeOptionalProperty( + String name, + String getterName, + Optional that, + XMLStreamWriter writer, + XmlCommon.ContentWriter writeContent) { + final T value = that.orElse(null); + if (value != null) { + writeProperty(name, getterName, value, writer, writeContent); + } + } + + /** + * Write {@code that} as its own, self-describing XML element. + * + *

Which element that is, is decided by the run-time type of + * {@code that}, so this one writer serves every abstract class, every + * concrete class with descendants, and the item of a list or of a tuple of + * any class at all. The reading, which has to decide what to construct + * before it has read anything, needs a dispatcher per interface instead. + * + *

An element written from here is nested in another one by + * construction, so it goes through the visitor which does not re-declare + * the XML namespace. + */ + private static void writeClass( + IClass that, + XMLStreamWriter writer) { + NESTED.visit(that, writer); + } + + private static void writeListOf_IClass( + List that, + XMLStreamWriter writer) { + int index = 0; + try { + for (IClass item : that) { + writeClass(item, writer); + index++; + } + } catch (XmlCommon.SerializeFailure failure) { + failure.getError().prependSegment( + new Reporting.IndexSegment(index)); + throw failure; + } + } + + private static void writeSomeItemAsSequence( + ISomeItem that, + XMLStreamWriter writer) { + writeProperty( + "name", + "getName()", + that.getName(), + writer, + XmlCommon::writeStringifiedContent); + + writeOptionalProperty( + "nickname", + "getNickname()", + that.getNickname(), + writer, + XmlCommon::writeStringifiedContent); + } + + @Override + public void visitSomeItem( + ISomeItem that, + XMLStreamWriter writer) { + XmlCommon.writeElement( + "someItem", + that, + writer, + withNamespace, + _VisitorWithWriter::writeSomeItemAsSequence); + } + + private static void writeSpecialItemAsSequence( + ISpecialItem that, + XMLStreamWriter writer) { + writeProperty( + "name", + "getName()", + that.getName(), + writer, + XmlCommon::writeStringifiedContent); + + writeOptionalProperty( + "nickname", + "getNickname()", + that.getNickname(), + writer, + XmlCommon::writeStringifiedContent); + + writeProperty( + "level", + "getLevel()", + that.getLevel(), + writer, + XmlCommon::writeStringifiedContent); + } + + @Override + public void visitSpecialItem( + ISpecialItem that, + XMLStreamWriter writer) { + XmlCommon.writeElement( + "specialItem", + that, + writer, + withNamespace, + _VisitorWithWriter::writeSpecialItemAsSequence); + } + + private static void writeContainerAsSequence( + IContainer that, + XMLStreamWriter writer) { + writeProperty( + "items", + "getItems()", + that.getItems(), + writer, + _VisitorWithWriter::writeListOf_IClass); + + writeProperty( + "someItems", + "getSomeItems()", + that.getSomeItems(), + writer, + _VisitorWithWriter::writeListOf_IClass); + + writeOptionalProperty( + "specialItems", + "getSpecialItems()", + that.getSpecialItems(), + writer, + _VisitorWithWriter::writeListOf_IClass); + } + + @Override + public void visitContainer( + IContainer that, + XMLStreamWriter writer) { + XmlCommon.writeElement( + "container", + that, + writer, + withNamespace, + _VisitorWithWriter::writeContainerAsSequence); + } + + private static void writeWrapperAsSequence( + IWrapper that, + XMLStreamWriter writer) { + writeProperty( + "container", + "getContainer()", + that.getContainer(), + writer, + _VisitorWithWriter::writeContainerAsSequence); + } + + @Override + public void visitWrapper( + IWrapper that, + XMLStreamWriter writer) { + XmlCommon.writeElement( + "wrapper", + that, + writer, + withNamespace, + _VisitorWithWriter::writeWrapperAsSequence); + } + } + + /** + * Serialize instances of meta-model classes to XML. + */ + /** + *

+   * Here is an example how to serialize an instance of IItem:
+   * {@code
+   * IClass anInstance = new IItem(
+   *     ... some constructor arguments ...
+   * );
+   * XMLStreamWriter writer = xmlWriterFactory.createXMLStreamWriter(...some arguments...);
+   * Serialize.to(
+   *   anInstance,
+   *   writer);
+   * }
+   * 
+ */ + public static class Serialize + { + /** + * Serialize an instance of the meta-model to XML. + * + *

{@code writer} is flushed exactly once, here at the end. Nothing is + * flushed in-between, which is what lets {@link XMLStreamWriter} buffer, + * and the single flush at the end is what lets a failure of the underlying + * stream be reported as a {@link XmlCommon.SerializeException} from this method -- + * were it left to the caller, the failure would surface at their own flush, + * after the serialization has long returned. + * + *

The path of a {@link XmlCommon.SerializeException} is rendered as + * a Java access path, and not as the relative XPath the de-serialization + * reports: the caller invoked this on an instance, and not on + * a document, which has not been written yet. It names the getters and + * the indices leading to the culprit, so that + * {@code getSubmodelElements().get(0).getValue()} can be pasted as it + * stands. The outermost element is deliberately not named, as this method + * takes any {@link IClass} and the name would say nothing the caller does + * not already know. + */ + public static void to( + IClass that, + XMLStreamWriter writer) throws XmlCommon.SerializeException { + try { + _VisitorWithWriter.ROOT.visit( + that, writer); + writer.flush(); + } catch (XMLStreamException exception) { + throw new XmlCommon.SerializeException("", exception.getMessage()); + } catch (XmlCommon.SerializeFailure failure) { + final Reporting.Error error = failure.getError(); + throw new XmlCommon.SerializeException( + Reporting.generateJavaPath(error.getPathSegments()), + error.getCause()); + } + } + } +} + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/Common.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/Common.java new file mode 100644 index 000000000..b8d78465f --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/Common.java @@ -0,0 +1,150 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import dummy.reporting.Reporting; +import dummy.types.model.IClass; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +/** + * Provide common functionality to be re-used across different tests + * such as reading of environment variables. + */ +public class Common { + public static String TEST_DATA_DIR = Paths.get("test_data").toAbsolutePath().toString(); + + public static final String RECORD_MODE_ENVIRONMENT_VARIABLE_NAME = + "DUMMY_TESTS_RECORD_MODE"; + + // NOTE (empwilli): + // It is tedious to record manually all the expected error messages. + // Therefore we include this variable to steer the automatic recording. + // We intentionally inter-twine the recording code with the test code + // to keep them close to each other so that they are easier to maintain. + public static final boolean RECORD_MODE = + System.getenv("DUMMY_TESTS_RECORD_MODE") != null && ( + System.getenv(RECORD_MODE_ENVIRONMENT_VARIABLE_NAME).equalsIgnoreCase("true") || + System.getenv(RECORD_MODE_ENVIRONMENT_VARIABLE_NAME).equalsIgnoreCase("on") || + System.getenv(RECORD_MODE_ENVIRONMENT_VARIABLE_NAME).equalsIgnoreCase("1") || + System.getenv(RECORD_MODE_ENVIRONMENT_VARIABLE_NAME).equalsIgnoreCase("yes")); + + @SuppressWarnings("unchecked") + public static T mustFind(IClass container, Class type) { + if (type.isInstance(container)) { + return type.cast(container); + } + for (IClass current : container.descend()) { + if (type.isInstance(current)) { + return type.cast(current); + } + } + throw new IllegalStateException("No instance of " + type.getSimpleName() + " could be found"); + } + + public static List findPaths(Path path, String fileExtension) throws IOException { + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Path must be a directory!"); + } + + List result; + try (Stream walk = Files.walk(path)) { + result = + walk.filter(p -> !Files.isDirectory(p)) + .filter(f -> f.toString().endsWith(fileExtension)) + .collect(Collectors.toList()); + } + return result; + } + + public static List findDirs(Path path) throws IOException { + if (!Files.isDirectory(path)) { + throw new IllegalArgumentException("Path must be a directory!"); + } + + List result; + try (Stream walk = Files.walk(path)) { + result = walk.filter(p -> Files.isDirectory(p)).collect(Collectors.toList()); + } + return result; + } + + public static List asList(Iterable iterable) { + return StreamSupport.stream(iterable.spliterator(), false).collect(Collectors.toList()); + } + + public static void assertNoVerificationErrors(List errors, Path path) { + if (!errors.isEmpty()) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder + .append("Expected no errors when verifying the instance de-serialized from ") + .append(path.toString()) + .append(", ") + .append("but got ") + .append(errors.size()) + .append(" error(s):") + .append(System.lineSeparator()); + for (Reporting.Error error : errors) { + stringBuilder + .append(Reporting.generateJsonPath(error.getPathSegments())) + .append(": ") + .append(error.getCause()); + } + fail(stringBuilder.toString()); + } + } + + public static void assertEqualsExpectedOrRerecordVerificationErrors( + List errors, Path path) throws IOException { + if (errors.isEmpty()) { + fail("Expected at least one verification error when verifying " + path + ", but got none"); + } + final String got = + errors.stream() + .map( + error -> + Reporting.generateJsonPath(error.getPathSegments()) + ": " + error.getCause()) + .collect(Collectors.joining(";\n")); + final Path errorsPath = Paths.get(path + ".errors"); + if (RECORD_MODE) { + Files.write(errorsPath, got.getBytes(StandardCharsets.UTF_8)); + } else { + if (!Files.exists(errorsPath)) { + throw new FileNotFoundException( + "The file with the recorded errors does not exist: " + + errorsPath + + "; maybe you want to set the environment variable " + + Common.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME); + } + final String expected = String.join("\n", Files.readAllLines(errorsPath)); + assertEquals( + expected, + got, + "The expected verification errors do not match the actual ones for the file " + path); + } + } + + public static String trace(IClass instance) + { + return instance.getClass().getName(); + } +} // class Common + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJson.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJson.java new file mode 100644 index 000000000..be63ff1f7 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJson.java @@ -0,0 +1,98 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.jsonization.Jsonization; +import dummy.types.impl.*; +import dummy.types.model.*; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.*; +import java.io.IOException; +import java.nio.file.Path; + +public final class CommonJson { + public static JsonNode readFromFile(Path path) { + final ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode node; + try { + node = objectMapper.readTree(path.toFile()); + } catch (JsonParseException exception) { + throw new IllegalStateException( + "Expected the file to be a valid JSON, but it was not: " + + path + "; exception was: " + + exception); + } catch (JsonProcessingException exception) { + throw new IllegalStateException( + "Expected the file to be a valid JSON, but it was not: " + + path + "; exception was: " + + exception); + } catch (IOException exception) { + throw new IllegalStateException( + "Expected the file to be a valid JSON, but it was not: " + + path + "; exception was: " + + exception); + } + return node; + } + + /** + * Serialize something to a uniform JSON text + * such that we can use it for comparisons in the tests. + */ + public static JsonNode toJson(Object something) + { + if (something instanceof Boolean) { + return BooleanNode.valueOf((Boolean) something); + } else if (something instanceof Integer) { + return IntNode.valueOf((Integer) something); + } else if (something instanceof Double) { + return DoubleNode.valueOf((Double) something); + } else if (something instanceof String) { + return TextNode.valueOf((String) something); + } else if (something instanceof byte[]) { + return BinaryNode.valueOf((byte[]) something); + } else if (something instanceof IClass) { + return Jsonization.Serialize.toJsonObject((IClass) something); + } else { + throw new IllegalArgumentException( + "The conversion of type " + + something.getClass().toString() + + " to a JSON node has not been defined: " + + something.toString()); + } + } + + /** + * Infer the node kind of the JSON node. + */ + // TODO check whether this is really necessary + private static String getNodeKind(JsonNode node) + { + if (node.isArray()) { + return "array"; + } else if (node.isObject()) { + return "object"; + } else if (node.isValueNode()) { + return "value"; + } else { + throw new IllegalStateException( + "Unhandled JsonNode: " + node.getNodeType().toString()); + } + } +} // public final class CommonJson + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJsonization.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJsonization.java new file mode 100644 index 000000000..39ce9f1f9 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/CommonJsonization.java @@ -0,0 +1,129 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import dummy.jsonization.Jsonization; +import dummy.types.impl.*; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** + * Provide methods to load instances from JSON test data. + */ +public final class CommonJsonization { + public static SomeItem loadMaximalSomeItem() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem", + "maximal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeSomeItem(node); + } // public static SomeItem loadMaximalSomeItem + + public static SomeItem loadMinimalSomeItem() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem", + "minimal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeSomeItem(node); + } // public static SomeItem loadMinimalSomeItem + + public static SpecialItem loadMaximalSpecialItem() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem", + "maximal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeSpecialItem(node); + } // public static SpecialItem loadMaximalSpecialItem + + public static SpecialItem loadMinimalSpecialItem() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem", + "minimal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeSpecialItem(node); + } // public static SpecialItem loadMinimalSpecialItem + + public static Container loadMaximalContainer() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Container", + "maximal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeContainer(node); + } // public static Container loadMaximalContainer + + public static Container loadMinimalContainer() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Container", + "minimal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeContainer(node); + } // public static Container loadMinimalContainer + + public static Wrapper loadMaximalWrapper() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper", + "maximal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeWrapper(node); + } // public static Wrapper loadMaximalWrapper + + public static Wrapper loadMinimalWrapper() throws IOException { + final Path path = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper", + "minimal.json"); + + final JsonNode node = CommonJson.readFromFile(path); + + return Jsonization.Deserialize.deserializeWrapper(node); + } // public static Wrapper loadMinimalWrapper +} // class CommonJsonization + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestCopying.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestCopying.java new file mode 100644 index 000000000..7dbcdfdf2 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestCopying.java @@ -0,0 +1,265 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dummy.common.*; +import dummy.copying.Copying; +import dummy.types.enums.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import dummy.types.model.IClass; +import dummy.visitation.AbstractTransformerWithContext; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +public class TestCopying { + private static class _DeepEqualiser extends AbstractTransformerWithContext { + @Override + public Boolean transformSomeItem(ISomeItem that, IClass other) { + if (!(other instanceof SomeItem)) { + return false; + } + + SomeItem casted = (SomeItem) other; + + return ( + that.getName().equals(casted.getName()) + && that.getNickname().equals(casted.getNickname())); + } + + @Override + public Boolean transformSpecialItem(ISpecialItem that, IClass other) { + if (!(other instanceof SpecialItem)) { + return false; + } + + SpecialItem casted = (SpecialItem) other; + + return ( + that.getName().equals(casted.getName()) + && that.getNickname().equals(casted.getNickname()) + && that.getLevel().equals(casted.getLevel())); + } + + @Override + public Boolean transformContainer(IContainer that, IClass other) { + if (!(other instanceof Container)) { + return false; + } + + Container casted = (Container) other; + + return ( + deepEqualsListOf_IItem(that.getItems(), casted.getItems()) + && deepEqualsListOf_ISomeItem(that.getSomeItems(), casted.getSomeItems()) + && (that.getSpecialItems().isPresent() + ? casted.getSpecialItems().isPresent() + && deepEqualsListOf_ISpecialItem(that.getSpecialItems().get(), casted.getSpecialItems().get()) + : !casted.getSpecialItems().isPresent())); + } + + @Override + public Boolean transformWrapper(IWrapper that, IClass other) { + if (!(other instanceof Wrapper)) { + return false; + } + + Wrapper casted = (Wrapper) other; + + return ( + transform(that.getContainer(), casted.getContainer())); + } + + private Boolean deepEqualsListOf_IItem( + List that, + List other) { + if (that.size() != other.size()) { + return false; + } + + for (int i = 0; i < that.size(); i++) { + if (!transform(that.get(i), other.get(i))) { + return false; + } + } + + return true; + } + + private Boolean deepEqualsListOf_ISomeItem( + List that, + List other) { + if (that.size() != other.size()) { + return false; + } + + for (int i = 0; i < that.size(); i++) { + if (!transform(that.get(i), other.get(i))) { + return false; + } + } + + return true; + } + + private Boolean deepEqualsListOf_ISpecialItem( + List that, + List other) { + if (that.size() != other.size()) { + return false; + } + + for (int i = 0; i < that.size(); i++) { + if (!transform(that.get(i), other.get(i))) { + return false; + } + } + + return true; + } + } // class _DeepEqualiser + + private static final _DeepEqualiser DeepEqualiserInstance = new _DeepEqualiser(); + + private static Boolean SomeItemShallowEquals( + SomeItem that, + SomeItem other) { + return ( + that.getName().equals(other.getName()) + && that.getNickname().equals(other.getNickname())); + } + + private static Boolean SpecialItemShallowEquals( + SpecialItem that, + SpecialItem other) { + return ( + that.getName().equals(other.getName()) + && that.getNickname().equals(other.getNickname()) + && that.getLevel().equals(other.getLevel())); + } + + private static Boolean ContainerShallowEquals( + Container that, + Container other) { + return ( + that.getItems().equals(other.getItems()) + && that.getSomeItems().equals(other.getSomeItems()) + && that.getSpecialItems().equals(other.getSpecialItems())); + } + + private static Boolean WrapperShallowEquals( + Wrapper that, + Wrapper other) { + return that.getContainer().equals(other.getContainer()); + } + + private static Boolean SomeItemDeepEquals(SomeItem that, SomeItem other) { + return DeepEqualiserInstance.transform(that, other); + } + + private static Boolean SpecialItemDeepEquals(SpecialItem that, SpecialItem other) { + return DeepEqualiserInstance.transform(that, other); + } + + private static Boolean ContainerDeepEquals(Container that, Container other) { + return DeepEqualiserInstance.transform(that, other); + } + + private static Boolean WrapperDeepEquals(Wrapper that, Wrapper other) { + return DeepEqualiserInstance.transform(that, other); + } + + @Test + public void testSomeItemShallowCopy() throws IOException { + final SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + final SomeItem instanceCopy = Copying.shallow(instance); + + assertTrue( + SomeItemShallowEquals(instance, instanceCopy), + "SomeItem"); + } // public void testSomeItemShallowCopy + + @Test + public void testSomeItemDeepCopy() throws IOException { + final SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + final SomeItem instanceCopy = Copying.deep(instance); + + assertTrue( + SomeItemDeepEquals(instance, instanceCopy), + "SomeItem"); + } // public void testSomeItemDeepCopy + + @Test + public void testSpecialItemShallowCopy() throws IOException { + final SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + final SpecialItem instanceCopy = Copying.shallow(instance); + + assertTrue( + SpecialItemShallowEquals(instance, instanceCopy), + "SpecialItem"); + } // public void testSpecialItemShallowCopy + + @Test + public void testSpecialItemDeepCopy() throws IOException { + final SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + final SpecialItem instanceCopy = Copying.deep(instance); + + assertTrue( + SpecialItemDeepEquals(instance, instanceCopy), + "SpecialItem"); + } // public void testSpecialItemDeepCopy + + @Test + public void testContainerShallowCopy() throws IOException { + final Container instance = CommonJsonization.loadMaximalContainer(); + final Container instanceCopy = Copying.shallow(instance); + + assertTrue( + ContainerShallowEquals(instance, instanceCopy), + "Container"); + } // public void testContainerShallowCopy + + @Test + public void testContainerDeepCopy() throws IOException { + final Container instance = CommonJsonization.loadMaximalContainer(); + final Container instanceCopy = Copying.deep(instance); + + assertTrue( + ContainerDeepEquals(instance, instanceCopy), + "Container"); + } // public void testContainerDeepCopy + + @Test + public void testWrapperShallowCopy() throws IOException { + final Wrapper instance = CommonJsonization.loadMaximalWrapper(); + final Wrapper instanceCopy = Copying.shallow(instance); + + assertTrue( + WrapperShallowEquals(instance, instanceCopy), + "Wrapper"); + } // public void testWrapperShallowCopy + + @Test + public void testWrapperDeepCopy() throws IOException { + final Wrapper instance = CommonJsonization.loadMaximalWrapper(); + final Wrapper instanceCopy = Copying.deep(instance); + + assertTrue( + WrapperDeepEquals(instance, instanceCopy), + "Wrapper"); + } // public void testWrapperDeepCopy +} // class TestCopying + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendAndVisitorThrough.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendAndVisitorThrough.java new file mode 100644 index 000000000..15d6458ca --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendAndVisitorThrough.java @@ -0,0 +1,166 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.*; + +import dummy.types.impl.*; +import dummy.types.model.IClass; +import dummy.visitation.VisitorThrough; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class TestDescendAndVisitorThrough { + private class _TracingVisitorThrough extends VisitorThrough { + public final List log = new ArrayList<>(); + + @Override + public void visit(IClass that) { + log.add(Common.trace(that)); + super.visit(that); + } + } + + private void assertDescendAndVisitorThroughSame(IClass instance) + { + final List logFromDescend = new ArrayList<>(); + + for (IClass subInstance : instance.descend()) { + logFromDescend.add(Common.trace(subInstance)); + } + + final _TracingVisitorThrough visitor = new _TracingVisitorThrough(); + visitor.visit(instance); + final List traceFromVisitor = visitor.log; + + assertFalse(traceFromVisitor.isEmpty()); + + assertEquals(Common.trace(instance), traceFromVisitor.get(0)); + + traceFromVisitor.remove(0); + + assertTrue(traceFromVisitor.equals(logFromDescend)); + } + + private void compareOrRerecordTrace(IClass instance, Path expectedPath) throws IOException { + final StringBuilder stringBuilder = new StringBuilder(); + for (IClass descendant : instance.descend()) { + stringBuilder.append(Common.trace(descendant)); + } + + final String got = stringBuilder.toString(); + if (Common.RECORD_MODE) { + Files.createDirectories(expectedPath.getParent()); + Files.write(expectedPath, got.getBytes()); + } else { + if (!Files.exists(expectedPath)) { + throw new FileNotFoundException( + "The file with the recorded value does not exist: " + expectedPath); + } + final String expected = + Files.readAllLines(expectedPath).stream().collect(Collectors.joining("\n")); + assertEquals(expected.replace("\n", ""), got.replace("\n", "")); + } + } + + @Test + public void testDescendOfSomeItem() throws IOException { + final SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "Descend", + "SomeItem", + "maximal.json.trace")); + } // public void testDescendOfSomeItem + + @Test + public void testDescendAgainstVisitorThroughForSomeItem() throws IOException { + SomeItem instance = ( + CommonJsonization.loadMaximalSomeItem()); + + assertDescendAndVisitorThroughSame(instance); + } // public void testDescendAgainstVisitorThroughForSomeItem + + @Test + public void testDescendOfSpecialItem() throws IOException { + final SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "Descend", + "SpecialItem", + "maximal.json.trace")); + } // public void testDescendOfSpecialItem + + @Test + public void testDescendAgainstVisitorThroughForSpecialItem() throws IOException { + SpecialItem instance = ( + CommonJsonization.loadMaximalSpecialItem()); + + assertDescendAndVisitorThroughSame(instance); + } // public void testDescendAgainstVisitorThroughForSpecialItem + + @Test + public void testDescendOfContainer() throws IOException { + final Container instance = CommonJsonization.loadMaximalContainer(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "Descend", + "Container", + "maximal.json.trace")); + } // public void testDescendOfContainer + + @Test + public void testDescendAgainstVisitorThroughForContainer() throws IOException { + Container instance = ( + CommonJsonization.loadMaximalContainer()); + + assertDescendAndVisitorThroughSame(instance); + } // public void testDescendAgainstVisitorThroughForContainer + + @Test + public void testDescendOfWrapper() throws IOException { + final Wrapper instance = CommonJsonization.loadMaximalWrapper(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "Descend", + "Wrapper", + "maximal.json.trace")); + } // public void testDescendOfWrapper + + @Test + public void testDescendAgainstVisitorThroughForWrapper() throws IOException { + Wrapper instance = ( + CommonJsonization.loadMaximalWrapper()); + + assertDescendAndVisitorThroughSame(instance); + } // public void testDescendAgainstVisitorThroughForWrapper +} // class TestDescendAndVisitorThrough + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendOnce.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendOnce.java new file mode 100644 index 000000000..61050fc5a --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestDescendOnce.java @@ -0,0 +1,105 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.types.impl.*; +import dummy.types.model.IClass; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class TestDescendOnce { + private static void compareOrRerecordTrace(IClass instance, Path expectedPath) + throws IOException, FileNotFoundException { + final StringBuilder stringBuilder = new StringBuilder(); + for (IClass descendant : instance.descendOnce()) { + stringBuilder.append(Common.trace(descendant)); + } + + final String got = stringBuilder.toString(); + + if (Common.RECORD_MODE) { + Files.createDirectories(expectedPath.getParent()); + Files.write(expectedPath, got.getBytes()); + } else { + if (!Files.exists(expectedPath)) { + throw new FileNotFoundException( + "The file with the recorded trace does not exist: " + + expectedPath + + "; maybe you want to set the environment variable " + + Common.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME); + } + final String expected = + Files.readAllLines(expectedPath).stream().collect(Collectors.joining("\n")); + assertEquals(expected.replace("\n", ""), got.replace("\n", "")); + } + } + + @Test + public void testSomeItem() throws IOException { + SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "DescendOnce", + "SomeItem", + "maximal.json.trace")); + } // public void testSomeItem + + @Test + public void testSpecialItem() throws IOException { + SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "DescendOnce", + "SpecialItem", + "maximal.json.trace")); + } // public void testSpecialItem + + @Test + public void testContainer() throws IOException { + Container instance = CommonJsonization.loadMaximalContainer(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "DescendOnce", + "Container", + "maximal.json.trace")); + } // public void testContainer + + @Test + public void testWrapper() throws IOException { + Wrapper instance = CommonJsonization.loadMaximalWrapper(); + + compareOrRerecordTrace( + instance, + Paths.get( + Common.TEST_DATA_DIR, + "DescendOnce", + "Wrapper", + "maximal.json.trace")); + } // public void testWrapper +} // class TestDescendOnce + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestEnhancing.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestEnhancing.java new file mode 100644 index 000000000..f46a6d794 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestEnhancing.java @@ -0,0 +1,169 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.*; + +import dummy.enhancing.Enhancer; +import dummy.types.impl.*; +import dummy.types.model.IClass; +import java.io.IOException; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; +import org.junit.jupiter.api.Test; + +public class TestEnhancing { + public static class _Enhancement { + public final long someCustomId; + + public _Enhancement(long someCustomId) { + this.someCustomId = someCustomId; + } + } + + private Enhancer<_Enhancement> createEnhancer() { + AtomicLong lastCustomId = new AtomicLong(); + + Function> enhancementFactory = + iClass -> Optional.of(new _Enhancement(lastCustomId.incrementAndGet())); + + return new Enhancer<>(enhancementFactory); + } + + @Test + public void testSomeItem() throws IOException { + final SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + + final Enhancer<_Enhancement> enhancer = createEnhancer(); + + assert !enhancer.unwrap(instance).isPresent(); + + final IClass wrapped = enhancer.wrap(instance); + assertNotNull(wrapped); + + final Set idSet = new HashSet<>(); + idSet.add(enhancer.mustUnwrap(wrapped).someCustomId); + wrapped + .descend() + .forEach(descendant -> idSet.add(enhancer.mustUnwrap(descendant).someCustomId)); + + assertFalse(enhancer.unwrap(instance).isPresent()); + assertNotNull(wrapped); + assertEquals( + 1L, + idSet.stream() + .min(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing min value for wrapped."))); + assertEquals( + idSet.size(), + idSet.stream() + .max(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing max value for wrapped."))); + } // public void testSomeItem + + @Test + public void testSpecialItem() throws IOException { + final SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + + final Enhancer<_Enhancement> enhancer = createEnhancer(); + + assert !enhancer.unwrap(instance).isPresent(); + + final IClass wrapped = enhancer.wrap(instance); + assertNotNull(wrapped); + + final Set idSet = new HashSet<>(); + idSet.add(enhancer.mustUnwrap(wrapped).someCustomId); + wrapped + .descend() + .forEach(descendant -> idSet.add(enhancer.mustUnwrap(descendant).someCustomId)); + + assertFalse(enhancer.unwrap(instance).isPresent()); + assertNotNull(wrapped); + assertEquals( + 1L, + idSet.stream() + .min(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing min value for wrapped."))); + assertEquals( + idSet.size(), + idSet.stream() + .max(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing max value for wrapped."))); + } // public void testSpecialItem + + @Test + public void testContainer() throws IOException { + final Container instance = CommonJsonization.loadMaximalContainer(); + + final Enhancer<_Enhancement> enhancer = createEnhancer(); + + assert !enhancer.unwrap(instance).isPresent(); + + final IClass wrapped = enhancer.wrap(instance); + assertNotNull(wrapped); + + final Set idSet = new HashSet<>(); + idSet.add(enhancer.mustUnwrap(wrapped).someCustomId); + wrapped + .descend() + .forEach(descendant -> idSet.add(enhancer.mustUnwrap(descendant).someCustomId)); + + assertFalse(enhancer.unwrap(instance).isPresent()); + assertNotNull(wrapped); + assertEquals( + 1L, + idSet.stream() + .min(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing min value for wrapped."))); + assertEquals( + idSet.size(), + idSet.stream() + .max(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing max value for wrapped."))); + } // public void testContainer + + @Test + public void testWrapper() throws IOException { + final Wrapper instance = CommonJsonization.loadMaximalWrapper(); + + final Enhancer<_Enhancement> enhancer = createEnhancer(); + + assert !enhancer.unwrap(instance).isPresent(); + + final IClass wrapped = enhancer.wrap(instance); + assertNotNull(wrapped); + + final Set idSet = new HashSet<>(); + idSet.add(enhancer.mustUnwrap(wrapped).someCustomId); + wrapped + .descend() + .forEach(descendant -> idSet.add(enhancer.mustUnwrap(descendant).someCustomId)); + + assertFalse(enhancer.unwrap(instance).isPresent()); + assertNotNull(wrapped); + assertEquals( + 1L, + idSet.stream() + .min(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing min value for wrapped."))); + assertEquals( + idSet.size(), + idSet.stream() + .max(Comparator.comparing(Long::valueOf)) + .orElseThrow(() -> new IllegalStateException("Missing max value for wrapped."))); + } // public void testWrapper +} // class TestEnhancing +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfConcreteClasses.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfConcreteClasses.java new file mode 100644 index 000000000..e4cdae03e --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfConcreteClasses.java @@ -0,0 +1,527 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import dummy.jsonization.Jsonization; +import dummy.reporting.Reporting; +import dummy.types.impl.*; +import dummy.types.model.IClass; +import dummy.common.*; +import dummy.types.enums.*; +import dummy.types.model.*; +import dummy.verification.Verification; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class TestJsonizationOfConcreteClasses { + private static void assertSerializeDeserializeEqualsOriginal(JsonNode originalNode, IClass instance, Path path) + throws JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + + JsonNode serialized = null; + try { + serialized = Jsonization.Serialize.toJsonObject(instance); + } catch (Exception exception) { + fail("Expected no exception upon serialization of an instance " + + "de-serialized from " + path + ", but got: " + exception); + } + + if (serialized == null) { + fail( + "Unexpected null serialization of an instance from " + path); + } + + assertEquals(objectMapper.readTree(originalNode.toString()), objectMapper.readTree(serialized.toString())); + } + + private static void assertEqualsExpectedOrRerecordDeserializationException( + Jsonization.DeserializeException exception, + Path path) throws FileNotFoundException, IOException{ + if (exception == null) { + fail("Expected a Jsonization exception when de-serializing " + + path + + ", but got none."); + } else { + final Path exceptionPath = Paths.get(path + ".exception"); + final String got = exception.getMessage(); + if (Common.RECORD_MODE) { + Files.write(exceptionPath, got.getBytes(StandardCharsets.UTF_8)); + } else { + if (!Files.exists(exceptionPath)) { + throw new FileNotFoundException( + "The file with the recorded errors does not exist: " + + exceptionPath + + "; maybe you want to set the environment variable " + + Common.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME); + } + final String expected = + Files.readAllLines(exceptionPath).stream().collect(Collectors.joining("\n")); + assertEquals( + expected, + got, + "The expected exception does not match the actual one for the file " + path); + } + } + } + + @Test + public void testSomeItemOk() throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + + final Path searchPath = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem"); + final List paths = Common.findPaths(searchPath, ".json"); + + for (Path path : paths) { + final JsonNode node = objectMapper.readTree(path.toFile()); + final SomeItem instance = Jsonization.Deserialize.deserializeSomeItem(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + } + } // public void testSomeItemOk + + @Test + public void testSomeItemDeserializationFromNonObjectFail() throws IOException { + final JsonNode node = JsonNodeFactory.instance.textNode("INVALID"); + + Jsonization.DeserializeException exception = null; + try { + final SomeItem unused = Jsonization.Deserialize.deserializeSomeItem(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assert exception != null : "Expected an exception, but got none"; + assert exception.getMessage().startsWith("Expected a JsonObject, but got ") : + "Unexpected exception message: " + exception.getMessage(); + } // public void testSomeItemDeserializationFromNonObjectFail + + @Test + public void testSomeItemDeserializationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable"))) { + final Path clsDir = causeDir.resolve("SomeItem"); + + if (!Files.exists(clsDir)) { + // No examples of SomeItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + Jsonization.DeserializeException exception = null; + try { + final SomeItem var = Jsonization.Deserialize.deserializeSomeItem(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void testSomeItemDeserializationFail + + @Test + public void testSomeItemVerificationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve("SomeItem"); + + if (!Files.exists(clsDir)) { + // No examples of SomeItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + final SomeItem instance = Jsonization.Deserialize.deserializeSomeItem(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testSomeItemVerificationFail + + @Test + public void testSpecialItemOk() throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + + final Path searchPath = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem"); + final List paths = Common.findPaths(searchPath, ".json"); + + for (Path path : paths) { + final JsonNode node = objectMapper.readTree(path.toFile()); + final SpecialItem instance = Jsonization.Deserialize.deserializeSpecialItem(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + } + } // public void testSpecialItemOk + + @Test + public void testSpecialItemDeserializationFromNonObjectFail() throws IOException { + final JsonNode node = JsonNodeFactory.instance.textNode("INVALID"); + + Jsonization.DeserializeException exception = null; + try { + final SpecialItem unused = Jsonization.Deserialize.deserializeSpecialItem(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assert exception != null : "Expected an exception, but got none"; + assert exception.getMessage().startsWith("Expected a JsonObject, but got ") : + "Unexpected exception message: " + exception.getMessage(); + } // public void testSpecialItemDeserializationFromNonObjectFail + + @Test + public void testSpecialItemDeserializationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable"))) { + final Path clsDir = causeDir.resolve("SpecialItem"); + + if (!Files.exists(clsDir)) { + // No examples of SpecialItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + Jsonization.DeserializeException exception = null; + try { + final SpecialItem var = Jsonization.Deserialize.deserializeSpecialItem(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void testSpecialItemDeserializationFail + + @Test + public void testSpecialItemVerificationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve("SpecialItem"); + + if (!Files.exists(clsDir)) { + // No examples of SpecialItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + final SpecialItem instance = Jsonization.Deserialize.deserializeSpecialItem(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testSpecialItemVerificationFail + + @Test + public void testContainerOk() throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + + final Path searchPath = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Container"); + final List paths = Common.findPaths(searchPath, ".json"); + + for (Path path : paths) { + final JsonNode node = objectMapper.readTree(path.toFile()); + final Container instance = Jsonization.Deserialize.deserializeContainer(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + } + } // public void testContainerOk + + @Test + public void testContainerDeserializationFromNonObjectFail() throws IOException { + final JsonNode node = JsonNodeFactory.instance.textNode("INVALID"); + + Jsonization.DeserializeException exception = null; + try { + final Container unused = Jsonization.Deserialize.deserializeContainer(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assert exception != null : "Expected an exception, but got none"; + assert exception.getMessage().startsWith("Expected a JsonObject, but got ") : + "Unexpected exception message: " + exception.getMessage(); + } // public void testContainerDeserializationFromNonObjectFail + + @Test + public void testContainerDeserializationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable"))) { + final Path clsDir = causeDir.resolve("Container"); + + if (!Files.exists(clsDir)) { + // No examples of Container for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + Jsonization.DeserializeException exception = null; + try { + final Container var = Jsonization.Deserialize.deserializeContainer(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void testContainerDeserializationFail + + @Test + public void testContainerVerificationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve("Container"); + + if (!Files.exists(clsDir)) { + // No examples of Container for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + final Container instance = Jsonization.Deserialize.deserializeContainer(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testContainerVerificationFail + + @Test + public void testWrapperOk() throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + + final Path searchPath = Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper"); + final List paths = Common.findPaths(searchPath, ".json"); + + for (Path path : paths) { + final JsonNode node = objectMapper.readTree(path.toFile()); + final Wrapper instance = Jsonization.Deserialize.deserializeWrapper(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + } + } // public void testWrapperOk + + @Test + public void testWrapperDeserializationFromNonObjectFail() throws IOException { + final JsonNode node = JsonNodeFactory.instance.textNode("INVALID"); + + Jsonization.DeserializeException exception = null; + try { + final Wrapper unused = Jsonization.Deserialize.deserializeWrapper(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assert exception != null : "Expected an exception, but got none"; + assert exception.getMessage().startsWith("Expected a JsonObject, but got ") : + "Unexpected exception message: " + exception.getMessage(); + } // public void testWrapperDeserializationFromNonObjectFail + + @Test + public void testWrapperDeserializationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable"))) { + final Path clsDir = causeDir.resolve("Wrapper"); + + if (!Files.exists(clsDir)) { + // No examples of Wrapper for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + Jsonization.DeserializeException exception = null; + try { + final Wrapper var = Jsonization.Deserialize.deserializeWrapper(node); + } catch (Jsonization.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException( + exception, path); + } + } + } // public void testWrapperDeserializationFail + + @Test + public void testWrapperVerificationFail() throws IOException { + for (Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve("Wrapper"); + + if (!Files.exists(clsDir)) { + // No examples of Wrapper for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".json"); + for (Path path : paths) { + final JsonNode node = CommonJson.readFromFile(path); + + final Wrapper instance = Jsonization.Deserialize.deserializeWrapper(node); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testWrapperVerificationFail + + private static JsonNode loadTheFirstExpected(String modelType) throws IOException { + final List paths = + Common.findPaths( + Paths.get( + Common.TEST_DATA_DIR, + "Json", + "Expected", + modelType), + ".json"); + + if (paths.isEmpty()) { + fail("Expected at least one recorded example of " + modelType + ", but got none"); + } + + return CommonJson.readFromFile(paths.get(0)); + } + + @Test + public void testSpecialItemLevelSerializationOutofrange() throws IOException { + for (long value : new long[] {9007199254740992L, -9007199254740992L}) { + final SpecialItem instance = + Jsonization.Deserialize.deserializeSpecialItem( + loadTheFirstExpected("SpecialItem")); + + instance.setLevel(value); + + try { + Jsonization.Serialize.toJsonObject(instance); + fail( + "Expected the serialization to fail at " + + "getLevel()" + + ", but it succeeded"); + } catch (Jsonization.SerializeException exception) { + assertEquals( + "getLevel()", + exception.getPath().orElse(null)); + } + } + } // public void testSpecialItemLevelSerializationOutofrange +} // class TestJsonizationOfConcreteClasses + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfEnums.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfEnums.java new file mode 100644 index 000000000..28eb2f4ae --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfEnums.java @@ -0,0 +1,25 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.jsonization.Jsonization; +import dummy.types.enums.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import org.junit.jupiter.api.Test; + +public class TestJsonizationOfEnums { + +} // class TestJsonizationOfEnums + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfInterfaces.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfInterfaces.java new file mode 100644 index 000000000..a91cc73d0 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestJsonizationOfInterfaces.java @@ -0,0 +1,91 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.jsonization.Jsonization; +import dummy.reporting.*; +import dummy.types.impl.*; +import dummy.types.model.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class TestJsonizationOfInterfaces { + @Test + public void testRoundTripIItemFromSomeItem() + throws IOException, JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + + SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + final JsonNode jsonObject = Jsonization.Serialize.toJsonObject(instance); + + IItem anotherInstance = Jsonization.Deserialize.deserializeIItem( + jsonObject); + final JsonNode anotherJsonObject = Jsonization.Serialize.toJsonObject(anotherInstance); + + assertEquals( + objectMapper.readTree(jsonObject.toString()), objectMapper.readTree(anotherJsonObject.toString())); + } // void testRoundTripIItemFromSomeItem + + @Test + public void testRoundTripIItemFromSpecialItem() + throws IOException, JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + + SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + final JsonNode jsonObject = Jsonization.Serialize.toJsonObject(instance); + + IItem anotherInstance = Jsonization.Deserialize.deserializeIItem( + jsonObject); + final JsonNode anotherJsonObject = Jsonization.Serialize.toJsonObject(anotherInstance); + + assertEquals( + objectMapper.readTree(jsonObject.toString()), objectMapper.readTree(anotherJsonObject.toString())); + } // void testRoundTripIItemFromSpecialItem + + @Test + public void testRoundTripISomeItemFromSpecialItem() + throws IOException, JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + + SpecialItem instance = CommonJsonization.loadMaximalSpecialItem(); + final JsonNode jsonObject = Jsonization.Serialize.toJsonObject(instance); + + ISomeItem anotherInstance = Jsonization.Deserialize.deserializeISomeItem( + jsonObject); + final JsonNode anotherJsonObject = Jsonization.Serialize.toJsonObject(anotherInstance); + + assertEquals( + objectMapper.readTree(jsonObject.toString()), objectMapper.readTree(anotherJsonObject.toString())); + } // void testRoundTripISomeItemFromSpecialItem + + @Test + public void testRoundTripISomeItemFromSomeItem() + throws IOException, JsonProcessingException { + final ObjectMapper objectMapper = new ObjectMapper(); + + SomeItem instance = CommonJsonization.loadMaximalSomeItem(); + final JsonNode jsonObject = Jsonization.Serialize.toJsonObject(instance); + + ISomeItem anotherInstance = Jsonization.Deserialize.deserializeISomeItem( + jsonObject); + final JsonNode anotherJsonObject = Jsonization.Serialize.toJsonObject(anotherInstance); + + assertEquals( + objectMapper.readTree(jsonObject.toString()), objectMapper.readTree(anotherJsonObject.toString())); + } // void testRoundTripISomeItemFromSomeItem +} // class TestJsonizationOfInterfaces + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestOverXOrEmpty.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestOverXOrEmpty.java new file mode 100644 index 000000000..b30a97f2d --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestOverXOrEmpty.java @@ -0,0 +1,37 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.types.enums.*; +import dummy.types.impl.*; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.jupiter.api.Test; + +public class TestOverXOrEmpty { + @Test + public void testContainerOverSpecialItemsOrEmpty() throws IOException { + for (Container instance : new Container[] + { + CommonJsonization.loadMinimalContainer(), + CommonJsonization.loadMaximalContainer() + }) { + int length = instance.getSpecialItems().map(elem -> elem.size()).orElse(0); + AtomicInteger count = new AtomicInteger(); + instance.overSpecialItemsOrEmpty().forEach(i -> count.getAndIncrement()); + assertEquals(length , count.get()); + } + } // public void testContaineroverSpecialItemsOrEmpty +} // class TestOverXOrEmpty + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestStringHelpers.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestStringHelpers.java new file mode 100644 index 000000000..d4a31f9f9 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestStringHelpers.java @@ -0,0 +1,364 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.common.StringHelpers; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * Test {@code len}, the slicing of strings and {@code find} as used in + * the transpiled code. + * + *

The transpiled code follows the Python implementation, since Python is + * the language of the meta-model specifications. Hence, the lengths and + * the positions count the characters (code points). The expected values have + * been computed with Python, and all the SDKs test against the very same cases. + */ +public class TestStringHelpers { + @Test + @DisplayName("len(\'\') gives 0: empty text") + public void testLenEmptyText() { + assertEquals( + 0, + StringHelpers.len("")); + } + + @Test + @DisplayName("len(\'abc\') gives 3: ASCII text") + public void testLenAsciiText() { + assertEquals( + 3, + StringHelpers.len("abc")); + } + + @Test + @DisplayName("len(\'é-\') gives 2: character of two UTF-8 bytes") + public void testLenCharacterOfTwoUtf8Bytes() { + assertEquals( + 2, + StringHelpers.len("é-")); + } + + @Test + @DisplayName("len(\'😀-\') gives 2: character beyond the Basic Multilingual Plane") + public void testLenCharacterBeyondTheBasicMultilingualPlane() { + assertEquals( + 2, + StringHelpers.len("😀-")); + } + + @Test + @DisplayName("len(\'aé😀b😀\') gives 5: mixed characters") + public void testLenMixedCharacters() { + assertEquals( + 5, + StringHelpers.len("aé😀b😀")); + } + + @Test + @DisplayName("\'abcde\'[1:3] gives \'bc\': start and end") + public void testSliceStartAndEnd() { + assertEquals( + "bc", + StringHelpers.slice("abcde", 1, 3)); + } + + @Test + @DisplayName("\'abcde\'[:2] gives \'ab\': no start") + public void testSliceNoStart() { + assertEquals( + "ab", + StringHelpers.slice("abcde", 0, 2)); + } + + @Test + @DisplayName("\'abcde\'[3:] gives \'de\': no end") + public void testSliceNoEnd() { + assertEquals( + "de", + StringHelpers.slice("abcde", 3)); + } + + @Test + @DisplayName("\'abcde\'[:] gives \'abcde\': neither start nor end") + public void testSliceNeitherStartNorEnd() { + assertEquals( + "abcde", + StringHelpers.slice("abcde", 0)); + } + + @Test + @DisplayName("\'abcde\'[-2:] gives \'de\': negative start counts from the end") + public void testSliceNegativeStartCountsFromTheEnd() { + assertEquals( + "de", + StringHelpers.slice("abcde", -2)); + } + + @Test + @DisplayName("\'abcde\'[:-2] gives \'abc\': negative end counts from the end") + public void testSliceNegativeEndCountsFromTheEnd() { + assertEquals( + "abc", + StringHelpers.slice("abcde", 0, -2)); + } + + @Test + @DisplayName("\'abcde\'[-4:-1] gives \'bcd\': negative start and end") + public void testSliceNegativeStartAndEnd() { + assertEquals( + "bcd", + StringHelpers.slice("abcde", -4, -1)); + } + + @Test + @DisplayName("\'abcde\'[-10:2] gives \'ab\': negative start before the beginning is clamped") + public void testSliceNegativeStartBeforeTheBeginningIsClamped() { + assertEquals( + "ab", + StringHelpers.slice("abcde", -10, 2)); + } + + @Test + @DisplayName("\'abcde\'[2:10] gives \'cde\': end beyond the end is clamped") + public void testSliceEndBeyondTheEndIsClamped() { + assertEquals( + "cde", + StringHelpers.slice("abcde", 2, 10)); + } + + @Test + @DisplayName("\'abcde\'[10:] gives \'\': start beyond the end gives empty") + public void testSliceStartBeyondTheEndGivesEmpty() { + assertEquals( + "", + StringHelpers.slice("abcde", 10)); + } + + @Test + @DisplayName("\'abcde\'[3:1] gives \'\': start after end gives empty") + public void testSliceStartAfterEndGivesEmpty() { + assertEquals( + "", + StringHelpers.slice("abcde", 3, 1)); + } + + @Test + @DisplayName("\'abcde\'[-1:-3] gives \'\': negative start after negative end gives empty") + public void testSliceNegativeStartAfterNegativeEndGivesEmpty() { + assertEquals( + "", + StringHelpers.slice("abcde", -1, -3)); + } + + @Test + @DisplayName("\'abcde\'[5:5] gives \'\': start equal to end gives empty") + public void testSliceStartEqualToEndGivesEmpty() { + assertEquals( + "", + StringHelpers.slice("abcde", 5, 5)); + } + + @Test + @DisplayName("\'\'[0:0] gives \'\': empty text") + public void testSliceEmptyText() { + assertEquals( + "", + StringHelpers.slice("", 0, 0)); + } + + @Test + @DisplayName("\'\'[-1:] gives \'\': negative start on empty text") + public void testSliceNegativeStartOnEmptyText() { + assertEquals( + "", + StringHelpers.slice("", -1)); + } + + @Test + @DisplayName("\'\'[:5] gives \'\': end beyond the end of empty text") + public void testSliceEndBeyondTheEndOfEmptyText() { + assertEquals( + "", + StringHelpers.slice("", 0, 5)); + } + + @Test + @DisplayName("\'é-x\'[1:] gives \'-x\': after a character of two UTF-8 bytes") + public void testSliceAfterACharacterOfTwoUtf8Bytes() { + assertEquals( + "-x", + StringHelpers.slice("é-x", 1)); + } + + @Test + @DisplayName("\'aéb\'[1:2] gives \'é\': character of two UTF-8 bytes") + public void testSliceCharacterOfTwoUtf8Bytes() { + assertEquals( + "é", + StringHelpers.slice("aéb", 1, 2)); + } + + @Test + @DisplayName("\'a😀b\'[1:2] gives \'😀\': character beyond the Basic Multilingual Plane") + public void testSliceCharacterBeyondTheBasicMultilingualPlane() { + assertEquals( + "😀", + StringHelpers.slice("a😀b", 1, 2)); + } + + @Test + @DisplayName("\'😀-x\'[1:] gives \'-x\': after a character beyond the Basic Multilingual Plane") + public void testSliceAfterACharacterBeyondTheBasicMultilingualPlane() { + assertEquals( + "-x", + StringHelpers.slice("😀-x", 1)); + } + + @Test + @DisplayName("\'aé😀b😀\'[-3:-1] gives \'😀b\': negative start on characters beyond ASCII") + public void testSliceNegativeStartOnCharactersBeyondAscii() { + assertEquals( + "😀b", + StringHelpers.slice("aé😀b😀", -3, -1)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'b\') gives 1: found") + public void testFindFound() { + assertEquals(1, StringHelpers.find("abcabc", "b")); + } + + @Test + @DisplayName("\'abcabc\'.find(\'x\') gives -1: not found") + public void testFindNotFound() { + assertEquals(-1, StringHelpers.find("abcabc", "x")); + } + + @Test + @DisplayName("\'abcabc\'.find(\'\') gives 0: empty sub") + public void testFindEmptySub() { + assertEquals(0, StringHelpers.find("abcabc", "")); + } + + @Test + @DisplayName("\'ab\'.find(\'abc\') gives -1: sub longer than text") + public void testFindSubLongerThanText() { + assertEquals(-1, StringHelpers.find("ab", "abc")); + } + + @Test + @DisplayName("\'abcabc\'.find(\'b\', 2) gives 4: found after start") + public void testFindFoundAfterStart() { + assertEquals(4, StringHelpers.find("abcabc", "b", 2)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'b\', 5) gives -1: found only before start") + public void testFindFoundOnlyBeforeStart() { + assertEquals(-1, StringHelpers.find("abcabc", "b", 5)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'c\', -1) gives 5: negative start counts from the end") + public void testFindNegativeStartCountsFromTheEnd() { + assertEquals(5, StringHelpers.find("abcabc", "c", -1)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'a\', -3) gives 3: negative start finds the later occurrence") + public void testFindNegativeStartFindsTheLaterOccurrence() { + assertEquals(3, StringHelpers.find("abcabc", "a", -3)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'a\', -10) gives 0: negative start before the beginning is clamped") + public void testFindNegativeStartBeforeTheBeginningIsClamped() { + assertEquals(0, StringHelpers.find("abcabc", "a", -10)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'\', 6) gives 6: empty sub at the end") + public void testFindEmptySubAtTheEnd() { + assertEquals(6, StringHelpers.find("abcabc", "", 6)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'\', 7) gives -1: empty sub beyond the end gives -1") + public void testFindEmptySubBeyondTheEndGives1() { + assertEquals(-1, StringHelpers.find("abcabc", "", 7)); + } + + @Test + @DisplayName("\'abcabc\'.find(\'c\', 10) gives -1: start beyond the end gives -1") + public void testFindStartBeyondTheEndGives1() { + assertEquals(-1, StringHelpers.find("abcabc", "c", 10)); + } + + @Test + @DisplayName("\'\'.find(\'\') gives 0: empty sub in empty text") + public void testFindEmptySubInEmptyText() { + assertEquals(0, StringHelpers.find("", "")); + } + + @Test + @DisplayName("\'\'.find(\'\', 1) gives -1: empty sub beyond the end of empty text gives -1") + public void testFindEmptySubBeyondTheEndOfEmptyTextGives1() { + assertEquals(-1, StringHelpers.find("", "", 1)); + } + + @Test + @DisplayName("\'\'.find(\'x\') gives -1: not found in empty text") + public void testFindNotFoundInEmptyText() { + assertEquals(-1, StringHelpers.find("", "x")); + } + + @Test + @DisplayName("\'é-\'.find(\'-\') gives 1: after a character of two UTF-8 bytes") + public void testFindAfterACharacterOfTwoUtf8Bytes() { + assertEquals(1, StringHelpers.find("é-", "-")); + } + + @Test + @DisplayName("\'😀-\'.find(\'-\') gives 1: after a character beyond the Basic Multilingual Plane") + public void testFindAfterACharacterBeyondTheBasicMultilingualPlane() { + assertEquals(1, StringHelpers.find("😀-", "-")); + } + + @Test + @DisplayName("\'a😀b\'.find(\'😀\') gives 1: character beyond the Basic Multilingual Plane") + public void testFindCharacterBeyondTheBasicMultilingualPlane() { + assertEquals(1, StringHelpers.find("a😀b", "😀")); + } + + @Test + @DisplayName("\'😀a😀a\'.find(\'a\', 2) gives 3: start after a character beyond ASCII") + public void testFindStartAfterACharacterBeyondAscii() { + assertEquals(3, StringHelpers.find("😀a😀a", "a", 2)); + } + + @Test + @DisplayName("\'😀a😀a\'.find(\'😀\', -2) gives 2: negative start on characters beyond ASCII") + public void testFindNegativeStartOnCharactersBeyondAscii() { + assertEquals(2, StringHelpers.find("😀a😀a", "😀", -2)); + } + + @Test + @DisplayName("\'é😀\'.find(\'\', 3) gives -1: start beyond the end of characters beyond ASCII") + public void testFindStartBeyondTheEndOfCharactersBeyondAscii() { + assertEquals(-1, StringHelpers.find("é😀", "", 3)); + } +} // class TestStringHelpers + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestVerificationOfEnums.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestVerificationOfEnums.java new file mode 100644 index 000000000..7f09a60a0 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestVerificationOfEnums.java @@ -0,0 +1,26 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.reporting.Reporting; +import dummy.types.enums.*; +import dummy.verification.Verification; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.jupiter.api.Test; + +public class TestVerificationOfEnums { + +} // class TestVerificationOfEnums + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXOrDefault.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXOrDefault.java new file mode 100644 index 000000000..fb5cac770 --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXOrDefault.java @@ -0,0 +1,53 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.reporting.*; +import dummy.stringification.Stringification; +import dummy.types.enums.*; +import dummy.types.impl.*; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +public class TestXOrDefault { + private static void compareOrRerecordValue(Object value, Path expectedPath) throws IOException { + final ObjectMapper objectMapper = new ObjectMapper(); + JsonNode got = CommonJson.toJson(value); + + if (Common.RECORD_MODE) { + Files.createDirectories(expectedPath.getParent()); + Files.write(expectedPath, got.toString().getBytes()); + } + else + { + if (!Files.exists(expectedPath)) { + throw new FileNotFoundException( + "The file with the recorded value does not exist: " + expectedPath + "; " + + "maybe you want to set the environment variable " + + Common.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME + "?"); + } + + final JsonNode expected = objectMapper.readTree(expectedPath.toFile()); + assertEquals(objectMapper.readTree(expected.toString()), objectMapper.readTree(got.toString())); + } + } +} // class TestXOrDefault + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfConcreteClasses.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfConcreteClasses.java new file mode 100644 index 000000000..618a5b50c --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfConcreteClasses.java @@ -0,0 +1,647 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import dummy.reporting.Reporting; +import dummy.types.impl.*; +import dummy.types.model.IClass; +import dummy.verification.Verification; +import dummy.xmlcommon.XmlCommon; +import dummy.xmlization.Xmlization; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import javax.xml.stream.*; +import javax.xml.stream.events.XMLEvent; +import org.junit.jupiter.api.Test; + +public class TestXmlizationOfConcreteClasses { + public static Optional checkElementsEqual( + XMLEvent expected, String expectedContent, Map outputMap) { + switch (expected.getEventType()) { + case XMLStreamConstants.START_ELEMENT: + { + final String expectedName = expected.asStartElement().getName().getLocalPart(); + final Optional> got = + outputMap.entrySet().stream() + .filter( + entry -> + entry.getKey().isStartElement() + && entry + .getKey() + .asStartElement() + .getName() + .getLocalPart() + .equals(expectedName)) + .filter(entry -> entry.getValue().equals(expectedContent)) + .findAny(); + if (!got.isPresent()) { + final Reporting.Error error = + new Reporting.Error( + "Missing start element " + + expectedName + + " in with content: " + + expectedContent); + return Optional.of(error); + } + outputMap.remove(got.get().getKey()); + return Optional.empty(); + } + case XMLStreamConstants.END_ELEMENT: + { + final String expectedName = expected.asEndElement().getName().getLocalPart(); + final Optional> got = + outputMap.entrySet().stream() + .filter( + entry -> + entry.getKey().isEndElement() + && entry + .getKey() + .asEndElement() + .getName() + .getLocalPart() + .equals(expectedName)) + .findAny(); + if (!got.isPresent()) { + final Reporting.Error error = + new Reporting.Error("Missing end element " + expectedName); + return Optional.of(error); + } + outputMap.remove(got.get().getKey()); + return Optional.empty(); + } + default: + { + throw new IllegalStateException("Unexpected event type in check elements equal."); + } + } + } + + private static String readContent(XMLEventReader reader) throws XMLStreamException { + final StringBuilder content = new StringBuilder(); + while (reader.hasNext() && reader.peek().isCharacters() + && !reader.peek().asCharacters().isWhiteSpace() + || reader.peek().getEventType() == XMLStreamConstants.COMMENT) { + + if (reader.peek().isCharacters()) { + content.append(reader.peek().asCharacters().getData()); + } + reader.nextEvent(); + } + return content.toString(); + } + + private static Map buildElementsMap(XMLEventReader reader) throws XMLStreamException { + final Map result = new LinkedHashMap<>(); + while (reader.hasNext()) { + final XMLEvent current = reader.nextEvent(); + if (current.isStartElement()) { + result.put(current, readContent(reader)); + } else if (current.isEndElement()) { + result.put(current, ""); + } + } + return result; + } + + private static void assertSerializeDeserializeEqualsOriginal(IClass instance, Path path) + throws XMLStreamException, IOException { + // Serialize + final StringWriter stringOut = new StringWriter(); + final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); + final XMLStreamWriter xmlStreamWriter = outputFactory.createXMLStreamWriter(stringOut); + + Xmlization.Serialize.to(instance, xmlStreamWriter); + + final String outputText = stringOut.toString(); + + // Compare expected == output + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader outputReader = + xmlInputFactory.createXMLEventReader(new StringReader(outputText)); + final Map outputMap = buildElementsMap(outputReader); + + // check the namespace of the output + for (XMLEvent event : outputMap.keySet()) { + if (event.isStartElement()) { + assertTrue( + Xmlization.NAMESPACE.equals(event.asStartElement().getName().getNamespaceURI()), + "Unexpected namespace of " + event.asStartElement().getName()); + } + if (event.isEndElement()) { + assertTrue( + Xmlization.NAMESPACE.equals(event.asEndElement().getName().getNamespaceURI()), + "Unexpected namespace of " + event.asEndElement().getName()); + } + } + + final XMLEventReader expectedReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + final Map expectedMap = buildElementsMap(expectedReader); + + if (expectedMap.size() != outputMap.size()) { + fail( + "Mismatch in element size expected " + + expectedMap.size() + + " but got " + + outputMap.size()); + } + + expectedMap.forEach( + (xmlEvent, content) -> { + final Optional inequalityError = + checkElementsEqual(xmlEvent, content, outputMap); + inequalityError.ifPresent( + error -> + fail( + "The original XML from " + + path + + " is unequal the serialized XML: " + + error.getCause())); + }); + } + + private static void assertEqualsExpectedOrRerecordDeserializationException( + XmlCommon.DeserializeException exception, + Path path) throws IOException { + if (exception == null) { + fail("Expected a Xmlization exception when de-serializing " + path + ", but got none."); + } else { + final Path exceptionPath = Paths.get(path + ".exception"); + final String got = exception.getMessage(); + if (Common.RECORD_MODE) { + Files.write(exceptionPath, got.getBytes(StandardCharsets.UTF_8)); + } else { + if (!Files.exists(exceptionPath)) { + throw new FileNotFoundException( + "The file with the recorded exception does not exist: " + + exceptionPath + + "; maybe you want to set the environment variable" + + Common.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME + + "?"); + } + + final String expected = String.join("\n", Files.readAllLines(exceptionPath)); + assertEquals( + expected.replace("\n", ""), + got.replace("\n", ""), + "The expected exception does not match the actual one for the file " + path); + } + } + } + + @Test + public void testSomeItemOk() throws IOException, XMLStreamException { + final Path searchPath = + Paths.get(Common.TEST_DATA_DIR, "Xml", "Expected", "someItem"); + final List paths = Common.findPaths(searchPath, ".xml"); + + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final SomeItem instance = + Xmlization.Deserialize.deserializeSomeItem(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + + assertSerializeDeserializeEqualsOriginal(instance, path); + } + } // public void testSomeItemOk + + @Test + public void testSomeItemDeserializationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable"))) { + final Path clsDir = + causeDir.resolve("someItem"); + + if (!Files.exists(clsDir)) { + // No examples of SomeItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + XmlCommon.DeserializeException exception = null; + + try { + Xmlization.Deserialize.deserializeSomeItem(xmlReader); + } catch (XmlCommon.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException(exception, path); + } + } + } // public void testSomeItemDeserializationFail + + @Test + public void testSomeItemVerificationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve( + "someItem"); + + if (!Files.exists(clsDir)) { + // No examples of SomeItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final SomeItem instance = + Xmlization.Deserialize.deserializeSomeItem(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testSomeItemVerificationFail + + @Test + public void testSpecialItemOk() throws IOException, XMLStreamException { + final Path searchPath = + Paths.get(Common.TEST_DATA_DIR, "Xml", "Expected", "specialItem"); + final List paths = Common.findPaths(searchPath, ".xml"); + + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final SpecialItem instance = + Xmlization.Deserialize.deserializeSpecialItem(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + + assertSerializeDeserializeEqualsOriginal(instance, path); + } + } // public void testSpecialItemOk + + @Test + public void testSpecialItemDeserializationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable"))) { + final Path clsDir = + causeDir.resolve("specialItem"); + + if (!Files.exists(clsDir)) { + // No examples of SpecialItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + XmlCommon.DeserializeException exception = null; + + try { + Xmlization.Deserialize.deserializeSpecialItem(xmlReader); + } catch (XmlCommon.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException(exception, path); + } + } + } // public void testSpecialItemDeserializationFail + + @Test + public void testSpecialItemVerificationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve( + "specialItem"); + + if (!Files.exists(clsDir)) { + // No examples of SpecialItem for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final SpecialItem instance = + Xmlization.Deserialize.deserializeSpecialItem(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testSpecialItemVerificationFail + + @Test + public void testContainerOk() throws IOException, XMLStreamException { + final Path searchPath = + Paths.get(Common.TEST_DATA_DIR, "Xml", "Expected", "container"); + final List paths = Common.findPaths(searchPath, ".xml"); + + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final Container instance = + Xmlization.Deserialize.deserializeContainer(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + + assertSerializeDeserializeEqualsOriginal(instance, path); + } + } // public void testContainerOk + + @Test + public void testContainerDeserializationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable"))) { + final Path clsDir = + causeDir.resolve("container"); + + if (!Files.exists(clsDir)) { + // No examples of Container for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + XmlCommon.DeserializeException exception = null; + + try { + Xmlization.Deserialize.deserializeContainer(xmlReader); + } catch (XmlCommon.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException(exception, path); + } + } + } // public void testContainerDeserializationFail + + @Test + public void testContainerVerificationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve( + "container"); + + if (!Files.exists(clsDir)) { + // No examples of Container for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final Container instance = + Xmlization.Deserialize.deserializeContainer(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testContainerVerificationFail + + @Test + public void testWrapperOk() throws IOException, XMLStreamException { + final Path searchPath = + Paths.get(Common.TEST_DATA_DIR, "Xml", "Expected", "wrapper"); + final List paths = Common.findPaths(searchPath, ".xml"); + + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final Wrapper instance = + Xmlization.Deserialize.deserializeWrapper(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertNoVerificationErrors(errors, path); + + assertSerializeDeserializeEqualsOriginal(instance, path); + } + } // public void testWrapperOk + + @Test + public void testWrapperDeserializationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable"))) { + final Path clsDir = + causeDir.resolve("wrapper"); + + if (!Files.exists(clsDir)) { + // No examples of Wrapper for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + XmlCommon.DeserializeException exception = null; + + try { + Xmlization.Deserialize.deserializeWrapper(xmlReader); + } catch (XmlCommon.DeserializeException observedException) { + exception = observedException; + } + + assertEqualsExpectedOrRerecordDeserializationException(exception, path); + } + } + } // public void testWrapperDeserializationFail + + @Test + public void testWrapperVerificationFail() throws IOException, XMLStreamException { + for ( + Path causeDir : + Common.findDirs( + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Invalid"))) { + final Path clsDir = causeDir.resolve( + "wrapper"); + + if (!Files.exists(clsDir)) { + // No examples of Wrapper for the failure cause. + continue; + } + + final List paths = Common.findPaths(clsDir, ".xml"); + for (Path path : paths) { + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(Files.newInputStream(path)); + + final Wrapper instance = + Xmlization.Deserialize.deserializeWrapper(xmlReader); + + final Iterable errorIter = Verification.verify(instance); + final List errors = Common.asList(errorIter); + Common.assertEqualsExpectedOrRerecordVerificationErrors(errors, path); + } + } + } // public void testWrapperVerificationFail + + @Test + public void testDuplicatePropertyFails() throws IOException, XMLStreamException { + final Path path = + Paths.get( + Common.TEST_DATA_DIR, + "Xml", + "Expected", + "someItem", + "minimal.xml"); + + final String text = + new String(Files.readAllBytes(path), StandardCharsets.UTF_8); + + // We cut the element of the property out of the recorded example and put it + // in a second time, just before the closing tag of the instance. + final int start = text.indexOf(""); + + final String property; + if (start >= 0) { + final int end = text.indexOf("", start); + property = text.substring(start, end + 7); + } else { + // The element is written self-closing in the example, an empty list being + // the usual reason. We write that very element out ourselves. + property = ""; + } + + final int insertionIndex = + text.lastIndexOf(""); + + if (insertionIndex < 0) { + throw new IllegalStateException( + "We expect the recorded example to contain the closing tag " + + "" + ", but it does not: " + path); + } + + final String brokenText = + text.substring(0, insertionIndex) + property + text.substring(insertionIndex); + + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(new StringReader(brokenText)); + + XmlCommon.DeserializeException exception = null; + + try { + Xmlization.Deserialize.deserializeSomeItem(xmlReader); + } catch (XmlCommon.DeserializeException observedException) { + exception = observedException; + } + + if (exception == null) { + fail( + "Expected an exception when the property " + + "name" + + " is given twice, but got none"); + } + } // public void testDuplicatePropertyFails +} // class TestXmlizationOfConcreteClasses + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfInterfaces.java b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfInterfaces.java new file mode 100644 index 000000000..14b318f2f --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/src/test/java/TestXmlizationOfInterfaces.java @@ -0,0 +1,154 @@ +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ + +package dummy.tests; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import dummy.types.impl.*; +import dummy.types.model.*; +import dummy.xmlization.Xmlization; +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import javax.xml.stream.*; +import org.junit.jupiter.api.Test; + +public class TestXmlizationOfInterfaces { + @Test + public void testRoundTripIItemFromSomeItem() + throws IOException, XMLStreamException { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + final IItem instance = + CommonJsonization.loadMaximalSomeItem(); + + // The round-trip starts here. + final StringWriter stringOut = new StringWriter(); + final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); + final XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(stringOut); + + Xmlization.Serialize.to(instance, xmlWriter); + String outputText = stringOut.toString(); + + // De-serialize from XML + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(new StringReader(outputText)); + final IItem anotherInstance = + Xmlization.Deserialize.deserializeIItem(xmlReader); + + // Serialize back to XML + final StringWriter anotherStringOut = new StringWriter(); + final XMLStreamWriter anotherXmlWriter = outputFactory.createXMLStreamWriter(anotherStringOut); + Xmlization.Serialize.to(anotherInstance, anotherXmlWriter); + + // Compare + assertEquals(outputText, anotherStringOut.toString()); + } // void testRoundTripIItemFromSomeItem + + @Test + public void testRoundTripIItemFromSpecialItem() + throws IOException, XMLStreamException { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + final IItem instance = + CommonJsonization.loadMaximalSpecialItem(); + + // The round-trip starts here. + final StringWriter stringOut = new StringWriter(); + final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); + final XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(stringOut); + + Xmlization.Serialize.to(instance, xmlWriter); + String outputText = stringOut.toString(); + + // De-serialize from XML + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(new StringReader(outputText)); + final IItem anotherInstance = + Xmlization.Deserialize.deserializeIItem(xmlReader); + + // Serialize back to XML + final StringWriter anotherStringOut = new StringWriter(); + final XMLStreamWriter anotherXmlWriter = outputFactory.createXMLStreamWriter(anotherStringOut); + Xmlization.Serialize.to(anotherInstance, anotherXmlWriter); + + // Compare + assertEquals(outputText, anotherStringOut.toString()); + } // void testRoundTripIItemFromSpecialItem + + @Test + public void testRoundTripISomeItemFromSpecialItem() + throws IOException, XMLStreamException { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + final ISomeItem instance = + CommonJsonization.loadMaximalSpecialItem(); + + // The round-trip starts here. + final StringWriter stringOut = new StringWriter(); + final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); + final XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(stringOut); + + Xmlization.Serialize.to(instance, xmlWriter); + String outputText = stringOut.toString(); + + // De-serialize from XML + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(new StringReader(outputText)); + final ISomeItem anotherInstance = + Xmlization.Deserialize.deserializeISomeItem(xmlReader); + + // Serialize back to XML + final StringWriter anotherStringOut = new StringWriter(); + final XMLStreamWriter anotherXmlWriter = outputFactory.createXMLStreamWriter(anotherStringOut); + Xmlization.Serialize.to(anotherInstance, anotherXmlWriter); + + // Compare + assertEquals(outputText, anotherStringOut.toString()); + } // void testRoundTripISomeItemFromSpecialItem + + @Test + public void testRoundTripISomeItemFromSomeItem() + throws IOException, XMLStreamException { + // We load from JSON here just to jump-start the round trip. + // The round-trip goes then over XML. + final ISomeItem instance = + CommonJsonization.loadMaximalSomeItem(); + + // The round-trip starts here. + final StringWriter stringOut = new StringWriter(); + final XMLOutputFactory outputFactory = XMLOutputFactory.newFactory(); + final XMLStreamWriter xmlWriter = outputFactory.createXMLStreamWriter(stringOut); + + Xmlization.Serialize.to(instance, xmlWriter); + String outputText = stringOut.toString(); + + // De-serialize from XML + final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance(); + final XMLEventReader xmlReader = + xmlInputFactory.createXMLEventReader(new StringReader(outputText)); + final ISomeItem anotherInstance = + Xmlization.Deserialize.deserializeISomeItem(xmlReader); + + // Serialize back to XML + final StringWriter anotherStringOut = new StringWriter(); + final XMLStreamWriter anotherXmlWriter = outputFactory.createXMLStreamWriter(anotherStringOut); + Xmlization.Serialize.to(anotherInstance, anotherXmlWriter); + + // Compare + assertEquals(outputText, anotherStringOut.toString()); + } // void testRoundTripISomeItemFromSomeItem +} // class TestXmlizationOfInterfaces + +// package dummy.tests + +/* + * This code has been automatically generated by aas-core-codegen. + * Do NOT edit or append. + */ diff --git a/dev/test_data/main/java/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/java/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/java/expected/variance_of_containers/input/snippets/package.txt b/dev/test_data/main/java/expected/variance_of_containers/input/snippets/package.txt new file mode 100644 index 000000000..2995a4d0e --- /dev/null +++ b/dev/test_data/main/java/expected/variance_of_containers/input/snippets/package.txt @@ -0,0 +1 @@ +dummy \ No newline at end of file diff --git a/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/schema.json b/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/schema.json new file mode 100644 index 000000000..9d938eee3 --- /dev/null +++ b/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/schema.json @@ -0,0 +1,133 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DummyForTest", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Container" + } + ], + "$id": "https://dummy.com", + "definitions": { + "Container": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Item_choice" + } + }, + "someItems": { + "type": "array", + "items": { + "$ref": "#/definitions/SomeItem_choice" + } + }, + "specialItems": { + "type": "array", + "items": { + "$ref": "#/definitions/SpecialItem" + } + } + }, + "required": [ + "items", + "someItems" + ] + }, + "Item": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "nickname": { + "type": "string" + }, + "modelType": { + "$ref": "#/definitions/ModelType" + } + }, + "required": [ + "name", + "modelType" + ] + }, + "Item_choice": { + "oneOf": [ + { + "$ref": "#/definitions/SomeItem" + }, + { + "$ref": "#/definitions/SpecialItem" + } + ] + }, + "ModelType": { + "type": "string", + "enum": [ + "SomeItem", + "SpecialItem" + ] + }, + "SomeItem": { + "allOf": [ + { + "$ref": "#/definitions/SomeItem_abstract" + }, + { + "properties": { + "modelType": { + "const": "SomeItem" + } + } + } + ] + }, + "SomeItem_abstract": { + "$ref": "#/definitions/Item" + }, + "SomeItem_choice": { + "oneOf": [ + { + "$ref": "#/definitions/SomeItem" + }, + { + "$ref": "#/definitions/SpecialItem" + } + ] + }, + "SpecialItem": { + "allOf": [ + { + "$ref": "#/definitions/SomeItem_abstract" + }, + { + "properties": { + "level": { + "type": "integer" + }, + "modelType": { + "const": "SpecialItem" + } + }, + "required": [ + "level" + ] + } + ] + }, + "Wrapper": { + "type": "object", + "properties": { + "container": { + "$ref": "#/definitions/Container" + } + }, + "required": [ + "container" + ] + } + } +} \ No newline at end of file diff --git a/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/jsonschema/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/jsonschema/expected/variance_of_containers/input/snippets/schema_base.json b/dev/test_data/main/jsonschema/expected/variance_of_containers/input/snippets/schema_base.json new file mode 100644 index 000000000..9c60bfeb7 --- /dev/null +++ b/dev/test_data/main/jsonschema/expected/variance_of_containers/input/snippets/schema_base.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "title": "DummyForTest", + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/Container" + } + ] +} \ No newline at end of file diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common.py new file mode 100644 index 000000000..023ae394b --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common.py @@ -0,0 +1,138 @@ +"""Provide functionality used across the tests.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import base64 +import collections.abc +import difflib +import enum +import io +import os +import pathlib +import textwrap +from typing import Union, Sequence + + +import dummy.common as our_common +import dummy.types as our_types + + +_REPO_ROOT = pathlib.Path(os.path.realpath(__file__)).parent.parent.parent + + +#: Path to the directory which contains input and golden files +TEST_DATA_DIR = _REPO_ROOT / "test_data" + + +#: If set, the golden files in the tests should be re-recorded instead +#: of checked against. +RECORD_MODE = os.environ.get("DUMMY_TESTS_RECORD_MODE", "").lower() in ( + "1", + "on", + "true", +) + + +def record_or_check(path: pathlib.Path, got: str) -> None: + """ + Re-record or check that :paramref:`got` content matches the content of + :paramref:`path`. + + If :py:attr:`~RECORD_MODE` is set, the content of :paramref:`path` will be + simply overwritten with :paramref:`got` content, and no checks are performed. + + :param path: to the golden file + :param got: obtained content + :raise: :py:class:`AssertionError` if the contents do not match + """ + if RECORD_MODE: + path.parent.mkdir(exist_ok=True, parents=True) + path.write_text(got, encoding="utf-8") + else: + if not path.exists(): + raise FileNotFoundError( + f"The golden file could not be found: {path}; did you record it?" + ) + + expected = path.read_text(encoding="utf-8") + if expected != got: + writer = io.StringIO() + + diff = difflib.ndiff( + expected.splitlines(keepends=True), got.splitlines(keepends=True) + ) + + diff_text = "".join(diff) + writer.write( + f"""\ +The obtained content and the content of {path} do not match: +{diff_text}""" + ) + + raise AssertionError(writer.getvalue()) + + +def trace( + that: Union[ + bool, + int, + float, + str, + bytes, + enum.Enum, + our_types.Class, + Sequence[ + Union[bool, int, float, str, bytes, enum.Enum, our_types.Class] + ], + ], +) -> str: + """ + Generate a segment in a trace of an iteration. + + :param that: to be traced + :return: segment in the descent trace + """ + if isinstance(that, our_types.Class): + return that.__class__.__name__ + elif isinstance(that, (bool, int, float, str, enum.Enum)): + return str(that) + elif isinstance(that, bytes): + return base64.b64encode(that).decode("ascii") + elif isinstance(that, collections.abc.Sequence): + if len(that) == 0: + return "[]" + writer = io.StringIO() + writer.write("[\n") + for i, item in enumerate(that): + writer.write(textwrap.indent(trace(item), " ")) + + if i < len(that) - 1: + writer.write(",\n") + else: + writer.write("\n") + + writer.write("]") + return writer.getvalue() + else: + our_common.assert_never(that) + + +def trace_log_as_text_file_content(log: Sequence[str]) -> str: + """ + Convert the trace log to a text to be stored in a file. + + :param log: to be converted to text + :return: content of the file, including the new-line at the end + """ + writer = io.StringIO() + for entry in log: + writer.write(f"{entry}\n") + writer.write("\n") + return writer.getvalue() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_jsonization.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_jsonization.py new file mode 100644 index 000000000..cd931e62f --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_jsonization.py @@ -0,0 +1,197 @@ +"""Provide common functionality for JSON de/serialization in tests.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import collections.abc +import json +import pathlib +import sys +from typing import Callable, Iterator, Mapping, Type + + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + + +import dummy.common as our_common +import dummy.jsonization as our_jsonization +import dummy.types as our_types + + +class Difference: + """Represent a single difference between two JSON-ables.""" + + #: Human-readable description of the difference + message: Final[str] + + #: Path in the expected JSON-able value which is different from + #: the obtained JSON-able value + path: Final[our_jsonization.Path] + + def __init__(self, message: str) -> None: + """Initialize with the given message and empty path.""" + self.message = message + self.path = our_jsonization.Path() + + def __str__(self) -> str: + return f"#{self.path}: {self.message}" + + +def check_equal( + expected: our_jsonization.Jsonable, + got: our_jsonization.Jsonable, +) -> Iterator[Difference]: + """ + Compare recursively two JSON-able values for equality. + + :param expected: expected JSON-able value + :param got: obtained JSON-able value + :yield: differences + """ + if isinstance(expected, (bool, int, float, str, bytes)): + if type(expected) != type(got): # pylint: disable=unidiomatic-typecheck + yield Difference(f"Expected {type(expected)}, but got {type(got)}") + + if expected != got: + yield Difference(f"Expected {expected!r}, but got {got!r}") + elif isinstance(expected, collections.abc.Sequence): + if not isinstance(got, collections.abc.Sequence): + yield Difference(f"Expected a sequence, but got {type(got)}") + else: + if len(expected) != len(got): + yield Difference( + f"Expected a sequence of {len(expected)} item(s), " + f"but got {len(got)} item(s)" + ) + + for i, (expected_item, got_item) in enumerate(zip(expected, got)): + for difference in check_equal(expected_item, got_item): + difference.path._prepend(our_jsonization.IndexSegment(expected, i)) + yield difference + + elif isinstance(expected, collections.abc.Mapping): + if not isinstance(got, collections.abc.Mapping): + yield Difference(f"Expected a mapping, but got {type(got)}") + else: + if not all(isinstance(key, str) for key in expected.keys()): + raise ValueError( + f"Expected all keys in the expected JSON-able value to be strings, " + f"but got: {list(expected.keys())}" + ) + + if not all(isinstance(key, str) for key in got.keys()): + yield Difference( + f"Expected all keys in a mapping to be strings, " + f"but got: {list(got.keys())}" + ) + + expected_key_set = set(expected.keys()) + got_key_set = set(got.keys()) + + expected_got_diff = expected_key_set.difference(got_key_set) + if expected_got_diff: + yield Difference(f"Expected key(s) {sorted(expected_got_diff)} missing") + + got_expected_diff = got_key_set.difference(expected_key_set) + if got_expected_diff: + yield Difference(f"Unexpected key(s) {sorted(got_expected_diff)}") + + for key, expected_value in expected.items(): + got_value = got[key] + + for difference in check_equal(expected_value, got_value): + difference.path._prepend( + our_jsonization.PropertySegment(expected, key) + ) + yield difference + else: + our_common.assert_never(expected) + + +_MODEL_TYPE_TO_FROM_JSONABLE: Mapping[ + str, + Callable[[our_jsonization.Jsonable], our_types.Class] +] = { + 'Container': + our_jsonization.container_from_jsonable, + 'SomeItem': + our_jsonization.some_item_from_jsonable, + 'SpecialItem': + our_jsonization.special_item_from_jsonable, + 'Wrapper': + our_jsonization.wrapper_from_jsonable +} + + +_MODEL_TYPE_TO_CLASS: Mapping[ + str, + Type[our_types.Class] +] = { + 'Container': + our_types.Container, + 'SomeItem': + our_types.SomeItem, + 'SpecialItem': + our_types.SpecialItem, + 'Wrapper': + our_types.Wrapper +} + + +def must_load( + path: pathlib.Path, + model_type: str +) -> our_types.Class: + """ + Load an instance from ``path``. + + The class of the instance must correspond to the ``model_type``. + """ + try: + jsonable = json.loads( + path.read_text(encoding='utf-8') + ) + except Exception as exception: + raise RuntimeError(f"Failed to read from {path}") from exception + + from_jsonable = _MODEL_TYPE_TO_FROM_JSONABLE.get(model_type, None) + if from_jsonable is None: + raise AssertionError( + "The model type is not mapped to " + f"the from_jsonable function: {model_type!r}" + ) + + try: + instance = from_jsonable(jsonable) + except Exception as exception: + raise RuntimeError( + "Failed to parse the instance " + f"with model type {model_type} from {path}" + ) from exception + + cls = _MODEL_TYPE_TO_CLASS.get(model_type, None) + if cls is None: + raise AssertionError( + "The model type is not mapped to the class: {model_type!r}" + ) + + if not isinstance(instance, cls): + raise RuntimeError( + f"Expected an instance with model type {model_type!r}, " + f"corresponding to class {cls}, " + f"but got an instance {instance}" + ) + + return instance + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_xmlization.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_xmlization.py new file mode 100644 index 000000000..bf62d4b5e --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/common_xmlization.py @@ -0,0 +1,186 @@ +"""Provide common functionality for XML de/serialization in tests.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import io +import pathlib +import sys +from typing import Iterator, Optional +import xml.etree.ElementTree as ET + + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + + +import dummy.types as our_types +import dummy.xmlization as our_xmlization + + +class Difference: + """Represent a single difference between two XML documents.""" + + #: Human-readable description of the difference + message: Final[str] + + #: Path to the expected XML element which is different from + #: the obtained XML element + path: Final[our_xmlization.Path] + + def __init__(self, message: str) -> None: + """Initialize with the given message and empty path.""" + self.message = message + self.path = our_xmlization.Path() + + def __str__(self) -> str: + return f"#{self.path}: {self.message}" + + +def remove_redundant_whitespace(element: ET.Element) -> None: + """ + Remove the whitespace which will be ignored in the XML parsing. + + :param element: to remove the whitespace from + """ + if len(element) > 0: + if element.text is not None and len(element.text.strip()) == 0: + element.text = None + + for child in element: + if child.tail is not None and len(child.tail.strip()) == 0: + child.tail = None + + remove_redundant_whitespace(child) + + +def check_equal( + expected: ET.Element, + got: ET.Element, +) -> Iterator[Difference]: + """ + Compare recursively two XML elements for equality. + + Make sure you called :py:function:`.remove_redundant_whitespace` + so that your comparison does not trip over trivial whitespace. + + :param expected: expected XML element + :param got: obtained XML element + :yield: differences + """ + # NOTE (mristin): + # We need to ignore the white-space before the children as it is ignored + # by the XML parser. + + stop_recursion = False + if expected.text is not None and got.text is None: + stop_recursion = True + yield Difference(f"Expected text {expected.text!r}, but got none") + + if expected.text is None and got.text is not None: + stop_recursion = True + yield Difference(f"Expected no text, but got {got.text!r}") + + if expected.tail is not None and got.tail is None: + stop_recursion = True + yield Difference(f"Expected tail {expected.tail!r}, but got none") + + if expected.tail is None and got.tail is not None: + stop_recursion = True + yield Difference(f"Expected no tail, but got {got.tail!r}") + + if expected.text is not None and got.text is not None and expected.text != got.text: + stop_recursion = True + yield Difference(f"Expected text {expected.text!r}, but got {got.text!r}") + + if expected.tail is not None and got.tail is not None and expected.tail != got.tail: + stop_recursion = True + yield Difference(f"Expected tail {expected.tail!r}, but got {got.tail!r}") + + if expected.tag != got.tag: + stop_recursion = True + yield Difference(f"Expected tail {expected.tag!r}, but got {got.tag!r}") + + expected_children = [ # pylint: disable=unnecessary-comprehension + child for child in expected + ] + + got_children = [child for child in got] # pylint: disable=unnecessary-comprehension + + if len(expected_children) != len(got_children): + stop_recursion = True + yield Difference( + f"Expected {len(expected_children)} child element(s), " + f"but got {len(got_children)} child element(s)" + ) + + children_tag_unique = len(set(child.tag for child in expected_children)) == len( + expected_children + ) + + if stop_recursion: + return + + for i, (expected_child, got_child) in enumerate( + zip(expected_children, got_children) + ): + for difference in check_equal(expected_child, got_child): + if children_tag_unique: + difference.path._prepend(our_xmlization.ElementSegment(expected_child)) + else: + difference.path._prepend(our_xmlization.IndexSegment(expected_child, i)) + + yield difference + + +def assert_elements_equal( + expected: ET.Element, got: ET.Element, message_if_not_equal: Optional[str] = None +) -> None: + """ + Assert that the two elements are equal. + + Make sure you called :py:function:`.remove_redundant_whitespace` + so that your comparison does not trip over trivial whitespace. + + :param expected: what you expected + :param got: what you got + :param message_if_not_equal: description or identifier to help debugging + :raise: :py:class:`AssertionError` if the two elements are not equal + """ + findings_text = "\n".join( + str(difference) for difference in check_equal(expected=expected, got=got) + ) + + if len(findings_text) != 0: + writer = io.StringIO() + writer.write( + f"Expected two elements to be equal, but they are not. " + f"Differences related to the expected element:\n" + f"{findings_text}" + ) + + if message_if_not_equal is not None: + writer.write("\n\n") + writer.write(message_if_not_equal) + + raise AssertionError(writer.getvalue()) + + +def must_load( + path: pathlib.Path, +) -> our_types.Class: + """Load an instance from ``path``.""" + try: + instance = our_xmlization.from_file(path) + except Exception as exception: + raise RuntimeError(f"Failed to read from {path}") from exception + + return instance + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_and_pass_through_visitor.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_and_pass_through_visitor.py new file mode 100644 index 000000000..cc2af20ea --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_and_pass_through_visitor.py @@ -0,0 +1,112 @@ +""" +Test jointly :py:method:`dummy.types.Class.descend` and +:py:method:`dummy.types.PassThroughVisitor`. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +from typing import ( + List, + Sequence +) +import unittest + + +import dummy.types as our_types + + +import tests.common +import tests.common_xmlization + + +class _TracingVisitor(our_types.PassThroughVisitor): + """Visit the instances and trace them.""" + + def __init__(self) -> None: + """Initialize with an empty log.""" + self._log = [] # type: List[str] + + @property + def log(self) -> Sequence[str]: + """Get the tracing log.""" + return self._log + + def visit(self, that: our_types.Class) -> None: + self._log.append(tests.common.trace(that)) + super().visit(that) + + +def assert_tracing_logs_from_descend_and_visitor_are_the_same( + that: our_types.Class, test_case: unittest.TestCase +) -> None: + """ + Check that the tracing logs are the same when :paramref:`that` instance + is visited and when :paramref:`that` is ran through + :py:method:`our_types.Class.descend`. + + :param that: instance to be iterated over + :param test_case: in which this assertion runs + :raise: :py:class:`AssertionError` if the logs differ + """ + log_from_descend = [tests.common.trace(something) for something in that.descend()] + + visitor = _TracingVisitor() + visitor.visit(that) + log_from_visitor = visitor.log + + test_case.assertGreater(len(log_from_visitor), 0) + test_case.assertEqual(tests.common.trace(that), log_from_visitor[0]) + + # noinspection PyTypeChecker + test_case.assertListEqual(log_from_descend, log_from_visitor[1:]) # type: ignore + + +class TestWithExpectedMaximal(unittest.TestCase): + def test_descend_against_recorded_trace_log(self) -> None: + xml_expected_dir = tests.common.TEST_DATA_DIR / "Xml" / "Expected" + + for path in sorted(xml_expected_dir.glob("**/maximal.xml")): + instance = tests.common_xmlization.must_load(path) + + rel_path = path.relative_to(xml_expected_dir) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "descend_and_pass_through_visitor" + / rel_path.parent + / f"{rel_path.name}.trace" + ) + + log = [ + tests.common.trace(something) + for something in instance.descend() + ] + + got_text = tests.common.trace_log_as_text_file_content(log) + + tests.common.record_or_check(expected_path, got_text) + + def test_descend_against_visitor(self) -> None: + xml_expected_dir = tests.common.TEST_DATA_DIR / "Xml" / "Expected" + + for path in sorted(xml_expected_dir.glob("**/maximal.xml")): + instance = tests.common_xmlization.must_load(path) + + assert_tracing_logs_from_descend_and_visitor_are_the_same( + instance, + self + ) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_once.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_once.py new file mode 100644 index 000000000..4d85ef319 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_descend_once.py @@ -0,0 +1,49 @@ +"""Test :py:method:`dummy.types.Class.descend_once`.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import unittest + + +import tests.common +import tests.common_xmlization + + +class TestWithExpectedMaximal(unittest.TestCase): + def test_descend_once_against_recorded_trace_log(self) -> None: + xml_expected_dir = tests.common.TEST_DATA_DIR / "Xml" / "Expected" + + for path in sorted(xml_expected_dir.glob("**/maximal.xml")): + instance = tests.common_xmlization.must_load(path) + + rel_path = path.relative_to(xml_expected_dir) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "descend_once" + / rel_path.parent + / f"{rel_path.name}.trace" + ) + + log = [ + tests.common.trace(something) + for something in instance.descend_once() + ] + + got_text = tests.common.trace_log_as_text_file_content(log) + + tests.common.record_or_check(expected_path, got_text) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_over_X_or_empty.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_over_X_or_empty.py new file mode 100644 index 000000000..b3b90c3b3 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_over_X_or_empty.py @@ -0,0 +1,64 @@ +"""Test ``over_X_or_empty`` methods.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import pathlib +import unittest + + +import dummy.types as our_types + + +import tests.common +import tests.common_xmlization + + +class TestContainer(unittest.TestCase): + def test_over_special_items_or_empty_against_recorded(self) -> None: + for minimal_or_maximal in ["minimal", "maximal"]: + instance = tests.common_xmlization.must_load( + pathlib.Path( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'container' + / f"{minimal_or_maximal}.xml" + ) + ) + + assert isinstance( + instance, + our_types.Container + ) + + log = [ + tests.common.trace( + list(instance.over_special_items_or_empty()) + ) + ] + + got_text = tests.common.trace_log_as_text_file_content(log) + + expected_path = pathlib.Path( + tests.common.TEST_DATA_DIR + / "test_over_X_or_empty" + / 'container' + / f"on_{minimal_or_maximal}.xml" + / 'over_special_items_or_empty.trace' + ) + + tests.common.record_or_check(expected_path, got_text) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_x_or_default.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_x_or_default.py new file mode 100644 index 000000000..3538a9f6f --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_for_x_or_default.py @@ -0,0 +1,27 @@ +"""Test ``X_or_default`` methods.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import pathlib +import unittest + + +import dummy.types as our_types + + +import tests.common +import tests.common_xmlization + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_classes_with_descendants.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_classes_with_descendants.py new file mode 100644 index 000000000..8798d4cec --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_classes_with_descendants.py @@ -0,0 +1,86 @@ +"""Test JSON de/serialization of classes with descendants.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import json +import unittest + + +import dummy.jsonization as our_jsonization + + +import tests.common +import tests.common_jsonization + + +class TestRoundTrips(unittest.TestCase): + def test_item(self) -> None: + for descendant_model_type in [ + 'SomeItem', + 'SpecialItem' + ]: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / descendant_model_type + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.item_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatch = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + + self.assertListEqual([], list(map(str, mismatch))) + + def test_some_item(self) -> None: + for descendant_model_type in [ + 'SpecialItem' + ]: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / descendant_model_type + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.some_item_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatch = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + + self.assertListEqual([], list(map(str, mismatch))) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_concrete_classes.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_concrete_classes.py new file mode 100644 index 000000000..3f6b3a645 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_concrete_classes.py @@ -0,0 +1,166 @@ +"""Test JSON de/serialization of concrete classes.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import json +import math +from typing import Any +import unittest + + +import dummy.jsonization as our_jsonization + + +import tests.common +import tests.common_jsonization + + +class TestRoundTrips(unittest.TestCase): + def test_some_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'SomeItem' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.some_item_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatches = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + self.assertListEqual([], list(map(str, mismatches))) + + def test_special_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'SpecialItem' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.special_item_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatches = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + self.assertListEqual([], list(map(str, mismatches))) + + def test_container(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'Container' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.container_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatches = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + self.assertListEqual([], list(map(str, mismatches))) + + def test_wrapper(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'Wrapper' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + original_jsonable = json.load(fid) + + instance = our_jsonization.wrapper_from_jsonable( + original_jsonable + ) + + another_jsonable = our_jsonization.to_jsonable(instance) + + mismatches = tests.common_jsonization.check_equal( + original_jsonable, + another_jsonable + ) + self.assertListEqual([], list(map(str, mismatches))) + + +def _load_the_first_expected(model_type: str) -> Any: + """Load the first recorded example of the ``model_type``.""" + paths = sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / model_type + ).glob("**/*.json") + ) + + assert len(paths) > 0, ( + f"Expected at least one recorded example of {model_type}, but got none" + ) + + with paths[0].open("rt") as fid: + return json.load(fid) + + +class TestSerializationFailures(unittest.TestCase): + def test_special_item_level_out_of_range(self) -> None: + for value in [9007199254740992, -9007199254740992]: + instance = our_jsonization.special_item_from_jsonable( + _load_the_first_expected('SpecialItem') + ) + + instance.level = value + + with self.assertRaises( + our_jsonization.SerializationException + ) as context_manager: + our_jsonization.to_jsonable(instance) + + self.assertEqual( + '.level', + context_manager.exception.path + ) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_enums.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_enums.py new file mode 100644 index 000000000..fac1a9596 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_jsonization_of_enums.py @@ -0,0 +1,26 @@ +"""Test JSON de/serialization of enumerations.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import unittest + + +import dummy.jsonization as our_jsonization + + +class TestRoundTrips(unittest.TestCase): + pass + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_len_slicing_and_find.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_len_slicing_and_find.py new file mode 100644 index 000000000..31d330fc3 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_len_slicing_and_find.py @@ -0,0 +1,310 @@ +""" +Test ``len``, the slicing of strings and ``find`` as used in the transpiled code. + +The transpiled code follows the Python implementation, since Python is +the language of the meta-model specifications. Hence, the lengths and +the positions count the characters (code points). The other SDKs test against +the very same cases. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import unittest + + +class Test_len_slicing_and_find(unittest.TestCase): + def test_len_empty_text(self) -> None: + self.assertEqual( + 0, + len('') + ) + + def test_len_ascii_text(self) -> None: + self.assertEqual( + 3, + len('abc') + ) + + def test_len_character_of_two_utf_8_bytes(self) -> None: + self.assertEqual( + 2, + len('é-') + ) + + def test_len_character_beyond_the_basic_multilingual_plane(self) -> None: + self.assertEqual( + 2, + len('😀-') + ) + + def test_len_mixed_characters(self) -> None: + self.assertEqual( + 5, + len('aé😀b😀') + ) + + def test_slice_start_and_end(self) -> None: + self.assertEqual( + 'bc', + 'abcde'[1:3] + ) + + def test_slice_no_start(self) -> None: + self.assertEqual( + 'ab', + 'abcde'[:2] + ) + + def test_slice_no_end(self) -> None: + self.assertEqual( + 'de', + 'abcde'[3:] + ) + + def test_slice_neither_start_nor_end(self) -> None: + self.assertEqual( + 'abcde', + 'abcde'[:] + ) + + def test_slice_negative_start_counts_from_the_end(self) -> None: + self.assertEqual( + 'de', + 'abcde'[-2:] + ) + + def test_slice_negative_end_counts_from_the_end(self) -> None: + self.assertEqual( + 'abc', + 'abcde'[:-2] + ) + + def test_slice_negative_start_and_end(self) -> None: + self.assertEqual( + 'bcd', + 'abcde'[-4:-1] + ) + + def test_slice_negative_start_before_the_beginning_is_clamped(self) -> None: + self.assertEqual( + 'ab', + 'abcde'[-10:2] + ) + + def test_slice_end_beyond_the_end_is_clamped(self) -> None: + self.assertEqual( + 'cde', + 'abcde'[2:10] + ) + + def test_slice_start_beyond_the_end_gives_empty(self) -> None: + self.assertEqual( + '', + 'abcde'[10:] + ) + + def test_slice_start_after_end_gives_empty(self) -> None: + self.assertEqual( + '', + 'abcde'[3:1] + ) + + def test_slice_negative_start_after_negative_end_gives_empty(self) -> None: + self.assertEqual( + '', + 'abcde'[-1:-3] + ) + + def test_slice_start_equal_to_end_gives_empty(self) -> None: + self.assertEqual( + '', + 'abcde'[5:5] + ) + + def test_slice_empty_text(self) -> None: + self.assertEqual( + '', + ''[0:0] + ) + + def test_slice_negative_start_on_empty_text(self) -> None: + self.assertEqual( + '', + ''[-1:] + ) + + def test_slice_end_beyond_the_end_of_empty_text(self) -> None: + self.assertEqual( + '', + ''[:5] + ) + + def test_slice_after_a_character_of_two_utf_8_bytes(self) -> None: + self.assertEqual( + '-x', + 'é-x'[1:] + ) + + def test_slice_character_of_two_utf_8_bytes(self) -> None: + self.assertEqual( + 'é', + 'aéb'[1:2] + ) + + def test_slice_character_beyond_the_basic_multilingual_plane(self) -> None: + self.assertEqual( + '😀', + 'a😀b'[1:2] + ) + + def test_slice_after_a_character_beyond_the_basic_multilingual_plane(self) -> None: + self.assertEqual( + '-x', + '😀-x'[1:] + ) + + def test_slice_negative_start_on_characters_beyond_ascii(self) -> None: + self.assertEqual( + '😀b', + 'aé😀b😀'[-3:-1] + ) + + def test_find_found(self) -> None: + self.assertEqual( + 1, + 'abcabc'.find('b') + ) + + def test_find_not_found(self) -> None: + self.assertEqual( + -1, + 'abcabc'.find('x') + ) + + def test_find_empty_sub(self) -> None: + self.assertEqual( + 0, + 'abcabc'.find('') + ) + + def test_find_sub_longer_than_text(self) -> None: + self.assertEqual( + -1, + 'ab'.find('abc') + ) + + def test_find_found_after_start(self) -> None: + self.assertEqual( + 4, + 'abcabc'.find('b', 2) + ) + + def test_find_found_only_before_start(self) -> None: + self.assertEqual( + -1, + 'abcabc'.find('b', 5) + ) + + def test_find_negative_start_counts_from_the_end(self) -> None: + self.assertEqual( + 5, + 'abcabc'.find('c', -1) + ) + + def test_find_negative_start_finds_the_later_occurrence(self) -> None: + self.assertEqual( + 3, + 'abcabc'.find('a', -3) + ) + + def test_find_negative_start_before_the_beginning_is_clamped(self) -> None: + self.assertEqual( + 0, + 'abcabc'.find('a', -10) + ) + + def test_find_empty_sub_at_the_end(self) -> None: + self.assertEqual( + 6, + 'abcabc'.find('', 6) + ) + + def test_find_empty_sub_beyond_the_end_gives_1(self) -> None: + self.assertEqual( + -1, + 'abcabc'.find('', 7) + ) + + def test_find_start_beyond_the_end_gives_1(self) -> None: + self.assertEqual( + -1, + 'abcabc'.find('c', 10) + ) + + def test_find_empty_sub_in_empty_text(self) -> None: + self.assertEqual( + 0, + ''.find('') + ) + + def test_find_empty_sub_beyond_the_end_of_empty_text_gives_1(self) -> None: + self.assertEqual( + -1, + ''.find('', 1) + ) + + def test_find_not_found_in_empty_text(self) -> None: + self.assertEqual( + -1, + ''.find('x') + ) + + def test_find_after_a_character_of_two_utf_8_bytes(self) -> None: + self.assertEqual( + 1, + 'é-'.find('-') + ) + + def test_find_after_a_character_beyond_the_basic_multilingual_plane(self) -> None: + self.assertEqual( + 1, + '😀-'.find('-') + ) + + def test_find_character_beyond_the_basic_multilingual_plane(self) -> None: + self.assertEqual( + 1, + 'a😀b'.find('😀') + ) + + def test_find_start_after_a_character_beyond_ascii(self) -> None: + self.assertEqual( + 3, + '😀a😀a'.find('a', 2) + ) + + def test_find_negative_start_on_characters_beyond_ascii(self) -> None: + self.assertEqual( + 2, + '😀a😀a'.find('😀', -2) + ) + + def test_find_start_beyond_the_end_of_characters_beyond_ascii(self) -> None: + self.assertEqual( + -1, + 'é😀'.find('', 3) + ) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_verification.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_verification.py new file mode 100644 index 000000000..dff8a12db --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_verification.py @@ -0,0 +1,295 @@ +"""Test the verification of instances.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import json +import unittest + + +import dummy.jsonization as our_jsonization +import dummy.verification as our_verification + + +import tests.common + + +class TestVerificationOfValidInstances(unittest.TestCase): + def test_some_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'SomeItem' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.some_item_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) > 0: + self.fail( + f"Expected no errors when verifying the instance de-serialized " + f"from {path}, but got {len(errors)} error(s):\n" + + "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + ) + + def test_special_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'SpecialItem' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.special_item_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) > 0: + self.fail( + f"Expected no errors when verifying the instance de-serialized " + f"from {path}, but got {len(errors)} error(s):\n" + + "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + ) + + def test_container(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'Container' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.container_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) > 0: + self.fail( + f"Expected no errors when verifying the instance de-serialized " + f"from {path}, but got {len(errors)} error(s):\n" + + "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + ) + + def test_wrapper(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Expected" + / 'Wrapper' + ).glob("**/*.json") + ): + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.wrapper_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) > 0: + self.fail( + f"Expected no errors when verifying the instance de-serialized " + f"from {path}, but got {len(errors)} error(s):\n" + + "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + ) + + +class TestVerificationOfInvalidInstances(unittest.TestCase): + def test_some_item(self) -> None: + for cause_dir in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Unexpected" + / "Invalid" + ).iterdir() + ): + for path in sorted( + (cause_dir / 'SomeItem').glob("**/*.json") + ): + rel_path = path.relative_to(tests.common.TEST_DATA_DIR) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "VerificationError" + / rel_path.parent + / f"{rel_path.name}.errors" + ) + + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.some_item_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) == 0: + self.fail( + f"Expected at least one verification error " + f"when verifying the instance de-serialized " + f"from {path}, but got none" + ) + + got = "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + "\n" + + tests.common.record_or_check(expected_path, got) + + def test_special_item(self) -> None: + for cause_dir in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Unexpected" + / "Invalid" + ).iterdir() + ): + for path in sorted( + (cause_dir / 'SpecialItem').glob("**/*.json") + ): + rel_path = path.relative_to(tests.common.TEST_DATA_DIR) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "VerificationError" + / rel_path.parent + / f"{rel_path.name}.errors" + ) + + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.special_item_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) == 0: + self.fail( + f"Expected at least one verification error " + f"when verifying the instance de-serialized " + f"from {path}, but got none" + ) + + got = "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + "\n" + + tests.common.record_or_check(expected_path, got) + + def test_container(self) -> None: + for cause_dir in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Unexpected" + / "Invalid" + ).iterdir() + ): + for path in sorted( + (cause_dir / 'Container').glob("**/*.json") + ): + rel_path = path.relative_to(tests.common.TEST_DATA_DIR) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "VerificationError" + / rel_path.parent + / f"{rel_path.name}.errors" + ) + + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.container_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) == 0: + self.fail( + f"Expected at least one verification error " + f"when verifying the instance de-serialized " + f"from {path}, but got none" + ) + + got = "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + "\n" + + tests.common.record_or_check(expected_path, got) + + def test_wrapper(self) -> None: + for cause_dir in sorted( + ( + tests.common.TEST_DATA_DIR + / "Json" + / "Unexpected" + / "Invalid" + ).iterdir() + ): + for path in sorted( + (cause_dir / 'Wrapper').glob("**/*.json") + ): + rel_path = path.relative_to(tests.common.TEST_DATA_DIR) + + expected_path = ( + tests.common.TEST_DATA_DIR + / "VerificationError" + / rel_path.parent + / f"{rel_path.name}.errors" + ) + + with path.open("rt") as fid: + jsonable = json.load(fid) + + instance = our_jsonization.wrapper_from_jsonable(jsonable) + + errors = list(our_verification.verify(instance)) + + if len(errors) == 0: + self.fail( + f"Expected at least one verification error " + f"when verifying the instance de-serialized " + f"from {path}, but got none" + ) + + got = "\n".join( + f"{error.path}: {error.cause}" for error in errors + ) + "\n" + + tests.common.record_or_check(expected_path, got) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_classes_with_descendants.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_classes_with_descendants.py new file mode 100644 index 000000000..b35eaa4c4 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_classes_with_descendants.py @@ -0,0 +1,133 @@ +"""Test XML de/serialization of classes with descendants.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import io +import pathlib +import tempfile +import unittest +import xml.etree.ElementTree as ET + + +import dummy.xmlization as our_xmlization + + +import tests.common +import tests.common_xmlization + + +class TestRoundTrips(unittest.TestCase): + def test_item(self) -> None: + for xml_class_name_of_descendant in [ + 'someItem', + 'specialItem' + ]: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / xml_class_name_of_descendant + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = our_xmlization.item_from_iterparse(iterator) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = our_xmlization.item_from_stream(io.StringIO(text)) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = our_xmlization.item_from_file(path) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = our_xmlization.item_from_str(text) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + def test_some_item(self) -> None: + for xml_class_name_of_descendant in [ + 'specialItem' + ]: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / xml_class_name_of_descendant + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = our_xmlization.some_item_from_iterparse(iterator) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = our_xmlization.some_item_from_stream(io.StringIO(text)) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = our_xmlization.some_item_from_file(path) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = our_xmlization.some_item_from_str(text) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_concrete_classes.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_concrete_classes.py new file mode 100644 index 000000000..aead49acb --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dev/tests/test_xmlization_of_concrete_classes.py @@ -0,0 +1,314 @@ +"""Test XML de/serialization of concrete classes.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +# pylint: disable=missing-docstring + + +import io +import pathlib +import tempfile +import math +import unittest +import xml.etree.ElementTree as ET + + +import dummy.types as our_types +import dummy.xmlization as our_xmlization + + +import tests.common +import tests.common_xmlization + + +class TestRoundTrips(unittest.TestCase): + def test_some_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'someItem' + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = ( + our_xmlization.some_item_from_iterparse(iterator) + ) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = ( + our_xmlization + .some_item_from_stream( + io.StringIO(text) + ) + ) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = ( + our_xmlization + .some_item_from_file(path) + ) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = ( + our_xmlization + .some_item_from_str(text) + ) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + def test_special_item(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'specialItem' + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = ( + our_xmlization.special_item_from_iterparse(iterator) + ) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = ( + our_xmlization + .special_item_from_stream( + io.StringIO(text) + ) + ) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = ( + our_xmlization + .special_item_from_file(path) + ) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = ( + our_xmlization + .special_item_from_str(text) + ) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + def test_container(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'container' + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = ( + our_xmlization.container_from_iterparse(iterator) + ) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = ( + our_xmlization + .container_from_stream( + io.StringIO(text) + ) + ) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = ( + our_xmlization + .container_from_file(path) + ) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = ( + our_xmlization + .container_from_str(text) + ) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + def test_wrapper(self) -> None: + for path in sorted( + ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'wrapper' + ).glob("**/*.xml") + ): + text = path.read_text(encoding="utf-8") + et_concrete = ET.fromstring(text) + tests.common_xmlization.remove_redundant_whitespace(et_concrete) + + # region From iterparse + iterator = ET.iterparse(source=io.StringIO(text), events=["start", "end"]) + got_from_iterparse = ( + our_xmlization.wrapper_from_iterparse(iterator) + ) + + et_from_iterparse = ET.fromstring(our_xmlization.to_str(got_from_iterparse)) + tests.common_xmlization.remove_redundant_whitespace(et_from_iterparse) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_iterparse) + # endregion + + # region From stream + got_from_stream = ( + our_xmlization + .wrapper_from_stream( + io.StringIO(text) + ) + ) + et_from_stream = ET.fromstring(our_xmlization.to_str(got_from_stream)) + tests.common_xmlization.remove_redundant_whitespace(et_from_stream) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_stream) + # endregion + + # region From file + with tempfile.TemporaryDirectory() as tmp_dir: + path = pathlib.Path(tmp_dir) / "something.xml" + path.write_text(text, encoding="utf-8") + + got_from_file = ( + our_xmlization + .wrapper_from_file(path) + ) + et_from_file = ET.fromstring(our_xmlization.to_str(got_from_file)) + tests.common_xmlization.remove_redundant_whitespace(et_from_file) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_file) + # endregion + + # region From string + got_from_str = ( + our_xmlization + .wrapper_from_str(text) + ) + et_from_str = ET.fromstring(our_xmlization.to_str(got_from_str)) + tests.common_xmlization.remove_redundant_whitespace(et_from_str) + tests.common_xmlization.assert_elements_equal(et_concrete, et_from_str) + # endregion + + +class TestDuplicateProperty(unittest.TestCase): + """Test that a property given more than once is refused.""" + + def test_duplicate_property(self) -> None: + path = ( + tests.common.TEST_DATA_DIR + / "Xml" + / "Expected" + / 'someItem' + / "minimal.xml" + ) + + text = path.read_text(encoding="utf-8") + + start = text.find('') + if start >= 0: + end = text.find('', start) + duplicated = text[start:end + 7] + else: + # The element is written self-closing in the example, an empty list + # being the usual reason. We write that very element out ourselves. + duplicated = '' + + insertion_index = text.rfind('') + + self.assertGreaterEqual( + insertion_index, + 0, + "Expected the recorded example to contain the closing tag " + f", but it does not: {path}", + ) + + broken_text = ( + text[:insertion_index] + duplicated + text[insertion_index:] + ) + + with self.assertRaises(our_xmlization.DeserializationException): + our_xmlization.some_item_from_str(broken_text) + + +if __name__ == "__main__": + unittest.main() + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/common.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/common.py new file mode 100644 index 000000000..dff28a6ff --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/common.py @@ -0,0 +1,87 @@ +"""Provide common functions shared among the modules.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import collections.abc +from typing import ( + Any, + NoReturn, + Optional, + Sequence +) + + +def assert_never(value: NoReturn) -> NoReturn: + """ + Signal to mypy to perform an exhaustive matching. + + Please see the following page for more details: + https://hakibenita.com/python-mypy-exhaustive-checking + """ + assert False, f"Unhandled value: {value} ({type(value).__name__})" + + +def try_to_cast_to_array_like( + value: Any +) -> Optional[Sequence[Any]]: + """ + Try to cast :paramref:`value` to something like a JSON array. + + A mapping and a set are refused, as neither is a JSON array, and so are + a ``str``, a ``bytes`` and a ``bytearray``, which Python counts among + the sequences but JSON does not. + + Only a sequence is admitted, and never an arbitrary iterable. A one-shot + iterable such as a generator is spent by the first walk over it, so + a verification followed by a serialization would silently give an empty + array -- this function is the single place where all the modules agree on + what an array is, so the refusal has to hold for all of them. + + >>> assert try_to_cast_to_array_like(True) is None + + >>> assert try_to_cast_to_array_like(0) is None + + >>> assert try_to_cast_to_array_like(2.2) is None + + >>> assert try_to_cast_to_array_like("hello") is None + + >>> assert try_to_cast_to_array_like(b"hello") is None + + >>> try_to_cast_to_array_like([1, 2]) + [1, 2] + + >>> assert try_to_cast_to_array_like({"a": 3}) is None + + >>> assert try_to_cast_to_array_like(collections.OrderedDict()) is None + + >>> try_to_cast_to_array_like(range(1, 2)) + range(1, 2) + + >>> try_to_cast_to_array_like((1, 2)) + (1, 2) + + >>> assert try_to_cast_to_array_like({1, 2, 3}) is None + + >>> assert try_to_cast_to_array_like(iter([1, 2])) is None + """ + # NOTE (mristin): + # A ``list`` is what :py:mod:`json` gives us, and ``isinstance`` against + # a concrete class costs a fraction of ``isinstance`` against the abstract + # :py:class:`collections.abc.Sequence`, so we shortcut it here. + if isinstance(value, list): + return value + + if isinstance(value, (str, bytes, bytearray)): + return None + + if isinstance(value, collections.abc.Sequence): + return value + + return None + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/constants.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/constants.py new file mode 100644 index 000000000..e8183cde5 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/constants.py @@ -0,0 +1,17 @@ +"""Provide constant values of the meta-model.""" + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + +import sys +from typing import AbstractSet + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +import dummy.types as our_types + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/jsonization.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/jsonization.py new file mode 100644 index 000000000..893f7577a --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/jsonization.py @@ -0,0 +1,1034 @@ +""" +Provide de/serialization of meta-model classes to/from JSON. + +We can not use one-pass deserialization for JSON since the object +properties do not have fixed order, and hence we can not read +``modelType`` property ahead of the remaining properties. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import collections.abc +import json +import re +import sys +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Tuple, + TypeVar, + Union, +) + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +import dummy.common as our_common +import dummy.stringification as our_stringification +import dummy.types as our_types + + +# NOTE (mristin): +# The SDK defines three of these path vocabularies: this one, the one in +# :py:mod:`dummy.jsonization`, and the one in +# :py:mod:`dummy.xmlcommon`. +# They look alike, and it is tempting to merge them, but they are not +# interchangeable. +# +# Each of them points into a different thing, and every segment carries +# a back-pointer to the container it stepped through, so the type of that +# back-pointer differs from one to the next: +# +# * This one points into the instances which you built, so a property +# segment holds a class of the meta-model. +# * The jsonization's points into the JSON-able structure being read or +# written, so a property segment holds the JSON-able mapping instead: +# while a document is being parsed, the instance which the property would +# belong to does not exist yet. +# * The xmlcommon's points into the XML document, where there are no +# properties at all, only elements, so it names an element instead. +# +# The three also render differently: a path into the instances is a Python +# access expression, a path into a JSON-able structure starts at the root of +# the document and carries no leading dot, and a path into an XML document +# is a relative XPath. +# +# Merging them would mean either dropping the back-pointers, which have been +# part of the public API of this SDK since before these modules were split +# apart, or defining a single path over the union of all the segment kinds -- +# in which case every consumer would have to handle segments which can never +# occur in its world. Three small vocabularies which each say exactly what +# they can say cost less than one large one which lies about its range. + + +class PropertySegment: + """Represent a property on a path to the erroneous value.""" + + #: Instance that contains the property + instance: Final[Mapping[str, Any]] + + #: Name of the property + name: Final[str] + + def __init__( + self, + instance: Mapping[str, Any], + name: str + ) -> None: + """Initialize with the given values.""" + self.instance = instance + self.name = name + + +class IndexSegment: + """Represent an index access on a path to the erroneous value.""" + + #: Container that contains the item + container: Final[Iterable[Any]] + + #: Index of the item + index: Final[int] + + def __init__( + self, + container: Iterable[Any], + index: int + ) -> None: + """Initialize with the given values.""" + self.container = container + self.index = index + + +class KeySegment: + """ + Represent a member access on a path to the erroneous value. + + Unlike a :py:class:`PropertySegment`, which names a property of one of our + classes, a key names a member of an open JSON-able object. It is known only + at run time, and can be any string at all, so it is always rendered + as a subscript. + """ + + #: Mapping that contains the value at :py:attr:`~key` + mapping: Final[Mapping[str, Any]] + + #: Key of the value + key: Final[str] + + def __init__( + self, + mapping: Mapping[str, Any], + key: str + ) -> None: + """Initialize with the given values.""" + self.mapping = mapping + self.key = key + + +Segment = Union[PropertySegment, IndexSegment, KeySegment] + + +#: Match the property names which a path writes after a dot; the other names +#: are written in brackets as JSON strings, *e.g.*, ``["$eq"]``. +_VARIABLE_NAME_RE = re.compile(r"^[a-zA-Z_][a-zA-Z_0-9]*$") + + +class Path: + """Represent the relative path to the erroneous value.""" + + def __init__(self) -> None: + """Initialize as an empty path.""" + self._segments = [] # type: List[Segment] + + @property + def segments(self) -> Sequence[Segment]: + """Get the segments of the path.""" + return self._segments + + def _prepend(self, segment: Segment) -> None: + """Insert the :paramref:`segment` in front of other segments.""" + self._segments.insert(0, segment) + + def __str__(self) -> str: + if len(self._segments) == 0: + return "" + + parts = [] # type: List[str] + + iterator = iter(self._segments) + first = next(iterator) + if isinstance(first, PropertySegment): + if _VARIABLE_NAME_RE.match(first.name): + parts.append(first.name) + else: + parts.append(f"[{json.dumps(first.name, ensure_ascii=False)}]") + elif isinstance(first, IndexSegment): + parts.append(f"[{first.index}]") + elif isinstance(first, KeySegment): + parts.append(f"[{json.dumps(first.key, ensure_ascii=False)}]") + else: + our_common.assert_never(first) + + for segment in iterator: + if isinstance(segment, PropertySegment): + if _VARIABLE_NAME_RE.match(segment.name): + parts.append(f".{segment.name}") + else: + parts.append(f"[{json.dumps(segment.name, ensure_ascii=False)}]") + elif isinstance(segment, IndexSegment): + parts.append(f"[{segment.index}]") + elif isinstance(segment, KeySegment): + parts.append(f"[{json.dumps(segment.key, ensure_ascii=False)}]") + else: + our_common.assert_never(segment) + + return "".join(parts) + + +class DeserializationException(Exception): + """Signal that the JSON de-serialization could not be performed.""" + + #: Human-readable explanation of the exception's cause + cause: Final[str] + + #: Relative path to the erroneous value + path: Final[Path] + + def __init__( + self, + cause: str + ) -> None: + """Initialize with the given :paramref:`cause` and an empty path.""" + self.cause = cause + self.path = Path() + + +# NOTE (mristin): +# Recursive definitions are not yet available in mypy +# (see https://github.com/python/mypy/issues/731). We have to use ``Any`` +# here, instead of recursive type annotations. +Jsonable = Union[ + bool, + int, + float, + str, + Sequence[Any], + Mapping[str, Any] +] + + +MutableJsonable = Union[ + bool, + int, + float, + str, + List[Any], + MutableMapping[str, Any] +] + + +# region De-serialization + + +_ValueT = TypeVar("_ValueT") + +#: Parse a JSON-able value into a value of the meta-model +_Parser = Callable[ + [Jsonable], + _ValueT +] + + +def _as_mapping( + jsonable: Jsonable +) -> Mapping[str, Any]: + """ + Interpret :paramref:`jsonable` as a mapping. + + :param jsonable: JSON-able structure to be interpreted + :return: :paramref:`jsonable`, as a mapping + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + # NOTE (mristin): + # We check against ``dict`` first. That is what :py:mod:`json` gives us, and + # ``isinstance`` against a concrete class costs a fraction of ``isinstance`` + # against the abstract :py:class:`collections.abc.Mapping` -- measured on + # CPython 3.10, 59 ns against 274 ns -- on a check which runs once for every + # instance that we de-serialize. + # + # We give the mapping back, instead of only raising, so that the caller can + # go on with a narrowed type. ``mypy --strict`` does not narrow a union + # across a call which merely raises. + if ( + not isinstance(jsonable, dict) + and not isinstance(jsonable, collections.abc.Mapping) + ): + raise DeserializationException( + f"Expected a mapping, but got: {type(jsonable)}" + ) + + return jsonable + + +def _dispatch_from_jsonable( + jsonable: Jsonable, + dispatch: Mapping[str, _Parser[_ValueT]], + name: str +) -> _ValueT: + """ + Parse :paramref:`jsonable` by dispatching on its ``modelType``. + + :param jsonable: JSON-able structure to be parsed + :param dispatch: to parse a concrete instance, by its model type + :param name: of the parsed type, for the error message + :return: parsed instance + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + mapping = _as_mapping(jsonable) + + model_type = mapping.get("modelType", None) + if model_type is None: + raise DeserializationException( + "Expected the property modelType, but found none" + ) + + if not isinstance(model_type, str): + # NOTE (mristin): + # A model type of a wrong type is reported at the property, as the type + # errors of all the other properties are. + exception = DeserializationException( + f"Expected the property modelType to be a str, " + f"but got: {type(model_type)}" + ) + exception.path._prepend(PropertySegment(mapping, "modelType")) + raise exception + + parse = dispatch.get(model_type, None) + if parse is None: + raise DeserializationException( + f"Unexpected model type for {name}: {model_type}" + ) + + return parse(mapping) + + +def _int_from_jsonable( + jsonable: Jsonable +) -> int: + """ + Parse :paramref:`jsonable` as an integer. + + :param jsonable: JSON-able structure to be parsed + :return: parsed integer + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + # NOTE (mristin): + # ``bool`` is a subclass of ``int`` in Python, so it has to be excluded + # explicitly. Otherwise ``True`` would be read as ``1``. + if isinstance(jsonable, bool) or not isinstance(jsonable, int): + raise DeserializationException( + f"Expected an int, but got: {type(jsonable)}" + ) + return jsonable + + +def _str_from_jsonable( + jsonable: Jsonable +) -> str: + """ + Parse :paramref:`jsonable` as a string. + + :param jsonable: JSON-able structure to be parsed + :return: parsed string + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + if not isinstance(jsonable, str): + raise DeserializationException( + f"Expected a str, but got: {type(jsonable)}" + ) + return jsonable + + +def _list_from_jsonable( + jsonable: Jsonable, + parse_item: _Parser[_ValueT] +) -> List[_ValueT]: + """ + Parse :paramref:`jsonable` as a list, applying :paramref:`parse_item` on + every item. + + :param jsonable: JSON-able structure to be parsed + :param parse_item: to parse a single item of the array + :return: parsed list + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + array_like = our_common.try_to_cast_to_array_like(jsonable) + if array_like is None: + raise DeserializationException( + f"Expected something array-like, but got: {type(jsonable)}" + ) + + result = [] # type: List[_ValueT] + for i, jsonable_item in enumerate(array_like): + try: + item = parse_item(jsonable_item) + except DeserializationException as exception: + exception.path._prepend(IndexSegment(array_like, i)) + raise + + result.append(item) + + return result + + +def _list_of__item_from_jsonable( + jsonable: Jsonable +) -> List[our_types.Item]: + """ + Parse :paramref:`jsonable` as a list of + :py:class:`.types.Item`. + + :param jsonable: JSON-able structure to be parsed + :return: parsed list + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + return _list_from_jsonable( + jsonable, + item_from_jsonable + ) + + +def _list_of__some_item_from_jsonable( + jsonable: Jsonable +) -> List[our_types.SomeItem]: + """ + Parse :paramref:`jsonable` as a list of + :py:class:`.types.SomeItem`. + + :param jsonable: JSON-able structure to be parsed + :return: parsed list + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + return _list_from_jsonable( + jsonable, + some_item_from_jsonable + ) + + +def _list_of__special_item_from_jsonable( + jsonable: Jsonable +) -> List[our_types.SpecialItem]: + """ + Parse :paramref:`jsonable` as a list of + :py:class:`.types.SpecialItem`. + + :param jsonable: JSON-able structure to be parsed + :return: parsed list + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + return _list_from_jsonable( + jsonable, + special_item_from_jsonable + ) + + +def item_from_jsonable( + jsonable: Jsonable +) -> our_types.Item: + """ + Parse an instance of :py:class:`.types.Item` from the JSON-able + structure :paramref:`jsonable`. + + :param jsonable: structure to be parsed + :return: Concrete instance of :py:class:`.types.Item` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + return _dispatch_from_jsonable( + jsonable, + _ITEM_FROM_JSONABLE_DISPATCH, + 'Item' + ) + + +def some_item_from_jsonable( + jsonable: Jsonable +) -> our_types.SomeItem: + """ + Parse an instance of :py:class:`.types.SomeItem` from the JSON-able + structure :paramref:`jsonable`. + + :param jsonable: structure to be parsed + :return: Concrete instance of :py:class:`.types.SomeItem` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + return _dispatch_from_jsonable( + jsonable, + _SOME_ITEM_FROM_JSONABLE_DISPATCH, + 'SomeItem' + ) + + +def _some_item_from_jsonable_without_dispatch( + jsonable: Jsonable +) -> our_types.SomeItem: + """ + Parse an instance of :py:class:`.types.SomeItem` from the JSON-able + structure :paramref:`jsonable`. + + This function performs no dispatch! It is used to parse the properties + as-are, and already assumes the exact model type. Usually, this function + is called from within a dispatching function, and you never call it + directly. If you want to de-serialize an instance of + :py:class:`.types.SomeItem`, call + :py:func:`some_item_from_jsonable`. + + :param jsonable: structure to be parsed + :return: Parsed instance of :py:class:`.types.SomeItem` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + mapping = _as_mapping(jsonable) + + the_name: Optional[str] = None + the_nickname: Optional[str] = None + + unexpected_key: Optional[str] = None + + try: + for key, jsonable_value in mapping.items(): + if key == 'modelType': + # The dispatch has already matched the model type. + pass + elif key == 'name': + the_name = _str_from_jsonable(jsonable_value) + elif key == 'nickname': + the_nickname = _str_from_jsonable(jsonable_value) + else: + unexpected_key = key + break + except DeserializationException as exception: + exception.path._prepend( + PropertySegment(mapping, key) + ) + raise + + if unexpected_key is not None: + raise DeserializationException( + f"Unexpected property: {unexpected_key}" + ) + + if the_name is None: + raise DeserializationException( + "The required property 'name' is missing" + ) + + return our_types.SomeItem( + the_name, + the_nickname + ) + + +def special_item_from_jsonable( + jsonable: Jsonable +) -> our_types.SpecialItem: + """ + Parse an instance of :py:class:`.types.SpecialItem` from the JSON-able + structure :paramref:`jsonable`. + + :param jsonable: structure to be parsed + :return: Parsed instance of :py:class:`.types.SpecialItem` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + mapping = _as_mapping(jsonable) + + model_type = mapping.get('modelType', None) + if model_type is not None and not isinstance(model_type, str): + # NOTE (mristin): + # A model type of a wrong type is reported at the property, as the type + # errors of all the other properties are. + exception = DeserializationException( + f"Expected the property modelType to be a str, " + f"but got: {type(model_type)}" + ) + exception.path._prepend(PropertySegment(mapping, 'modelType')) + raise exception + + if model_type != 'SpecialItem': + raise DeserializationException( + f"Expected modelType to be 'SpecialItem', " + f"but got: {model_type!r}" + ) + + the_name: Optional[str] = None + the_nickname: Optional[str] = None + the_level: Optional[int] = None + + unexpected_key: Optional[str] = None + + try: + for key, jsonable_value in mapping.items(): + if key == 'modelType': + # The model type has already been checked above. + pass + elif key == 'name': + the_name = _str_from_jsonable(jsonable_value) + elif key == 'nickname': + the_nickname = _str_from_jsonable(jsonable_value) + elif key == 'level': + the_level = _int_from_jsonable(jsonable_value) + else: + unexpected_key = key + break + except DeserializationException as exception: + exception.path._prepend( + PropertySegment(mapping, key) + ) + raise + + if unexpected_key is not None: + raise DeserializationException( + f"Unexpected property: {unexpected_key}" + ) + + if the_name is None: + raise DeserializationException( + "The required property 'name' is missing" + ) + + if the_level is None: + raise DeserializationException( + "The required property 'level' is missing" + ) + + return our_types.SpecialItem( + the_name, + the_level, + the_nickname + ) + + +def container_from_jsonable( + jsonable: Jsonable +) -> our_types.Container: + """ + Parse an instance of :py:class:`.types.Container` from the JSON-able + structure :paramref:`jsonable`. + + :param jsonable: structure to be parsed + :return: Parsed instance of :py:class:`.types.Container` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + mapping = _as_mapping(jsonable) + + the_items: Optional[List[our_types.Item]] = None + the_some_items: Optional[List[our_types.SomeItem]] = None + the_special_items: Optional[List[our_types.SpecialItem]] = None + + unexpected_key: Optional[str] = None + + try: + for key, jsonable_value in mapping.items(): + if key == 'items': + the_items = _list_of__item_from_jsonable(jsonable_value) + elif key == 'someItems': + the_some_items = _list_of__some_item_from_jsonable(jsonable_value) + elif key == 'specialItems': + the_special_items = _list_of__special_item_from_jsonable(jsonable_value) + else: + unexpected_key = key + break + except DeserializationException as exception: + exception.path._prepend( + PropertySegment(mapping, key) + ) + raise + + if unexpected_key is not None: + raise DeserializationException( + f"Unexpected property: {unexpected_key}" + ) + + if the_items is None: + raise DeserializationException( + "The required property 'items' is missing" + ) + + if the_some_items is None: + raise DeserializationException( + "The required property 'someItems' is missing" + ) + + return our_types.Container( + the_items, + the_some_items, + the_special_items + ) + + +def wrapper_from_jsonable( + jsonable: Jsonable +) -> our_types.Wrapper: + """ + Parse an instance of :py:class:`.types.Wrapper` from the JSON-able + structure :paramref:`jsonable`. + + :param jsonable: structure to be parsed + :return: Parsed instance of :py:class:`.types.Wrapper` + :raise: :py:class:`DeserializationException` if unexpected :paramref:`jsonable` + """ + mapping = _as_mapping(jsonable) + + the_container: Optional[our_types.Container] = None + + unexpected_key: Optional[str] = None + + try: + for key, jsonable_value in mapping.items(): + if key == 'container': + the_container = container_from_jsonable(jsonable_value) + else: + unexpected_key = key + break + except DeserializationException as exception: + exception.path._prepend( + PropertySegment(mapping, key) + ) + raise + + if unexpected_key is not None: + raise DeserializationException( + f"Unexpected property: {unexpected_key}" + ) + + if the_container is None: + raise DeserializationException( + "The required property 'container' is missing" + ) + + return our_types.Wrapper( + the_container + ) + + +#: De-serialize a concrete instance of +#: :py:class:`.types.Item`, by its model type +_ITEM_FROM_JSONABLE_DISPATCH: Mapping[ + str, + _Parser[our_types.Item] +] = { + 'SomeItem': some_item_from_jsonable, + 'SpecialItem': special_item_from_jsonable, +} + + +#: De-serialize a concrete instance of +#: :py:class:`.types.SomeItem`, by its model type +_SOME_ITEM_FROM_JSONABLE_DISPATCH: Mapping[ + str, + _Parser[our_types.SomeItem] +] = { + 'SomeItem': _some_item_from_jsonable_without_dispatch, + 'SpecialItem': special_item_from_jsonable, +} + + +# endregion + + +# region Serialization + + +class SerializationException(Exception): + """Signal that the JSON serialization could not be performed.""" + + #: Human-readable explanation of the exception's cause + cause: Final[str] + + def __init__( + self, + cause: str + ) -> None: + """Initialize with the given :paramref:`cause` and an empty path.""" + self.cause = cause + self._segments = [] # type: List[str] + + @property + def path(self) -> str: + """ + Render the path to the erroneous value as a Python access expression. + + The path points into the instance which you handed over for + the serialization, and *not* into a JSON document -- at the point of + the failure, there is no document yet. For example, ``.submodels[0].value`` + tells you that the serialization broke on ``that.submodels[0].value``. + """ + return ''.join(self._segments) + + def _prepend_property(self, name: str) -> None: + """Insert the access to the property :paramref:`name` before the path.""" + self._segments.insert(0, f'.{name}') + + def _prepend_index(self, index: int) -> None: + """Insert the access to the item at :paramref:`index` before the path.""" + self._segments.insert(0, f'[{index}]') + + def _prepend_key(self, key: str) -> None: + """ + Insert the access to the member :paramref:`key` before the path. + + Unlike a property of one of our classes, a member of an open JSON-able + object is known only at run time and can be any string at all, so it is + always rendered as a subscript. + """ + self._segments.insert(0, f'[{json.dumps(key, ensure_ascii=False)}]') + + def __str__(self) -> str: + if len(self._segments) == 0: + return self.cause + + return f'{self.path}: {self.cause}' + + +def _int_to_jsonable( + that: int +) -> int: + """ + Serialize :paramref:`that` integer to a JSON-able value. + + Only the integers in the range :math:`[-2^{53} + 1, 2^{53} - 1]` are + serialized. Outside of it, an integer can not be exactly represented as + a 64-bit floating-point number, which is what the JSON de-serializers of + the other languages read a number into. + + :param that: integer to be serialized + :return: :paramref:`that`, unchanged + :raise: :py:class:`SerializationException` if outside the range + """ + if that < -9007199254740991 or that > 9007199254740991: + raise SerializationException( + f"The integer can not be serialized to JSON as it is outside " + f"the range [-2^53 + 1, 2^53 - 1]: {that}" + ) + return that + + +def _list_of__item_to_jsonable( + that: List[our_types.Item] +) -> List[MutableJsonable]: + """ + Serialize :paramref:`that` as a list of + :py:class:`.types.Item`. + + :param that: list to be serialized + :return: JSON-able representation of :paramref:`that` + """ + jsonable = [] # type: List[MutableJsonable] + for i, item in enumerate(that): + try: + jsonable.append( + item.transform(_SERIALIZER) + ) + except SerializationException as exception: + exception._prepend_index(i) + raise + return jsonable + + +def _list_of__some_item_to_jsonable( + that: List[our_types.SomeItem] +) -> List[MutableJsonable]: + """ + Serialize :paramref:`that` as a list of + :py:class:`.types.SomeItem`. + + :param that: list to be serialized + :return: JSON-able representation of :paramref:`that` + """ + jsonable = [] # type: List[MutableJsonable] + for i, item in enumerate(that): + try: + jsonable.append( + item.transform(_SERIALIZER) + ) + except SerializationException as exception: + exception._prepend_index(i) + raise + return jsonable + + +def _list_of__special_item_to_jsonable( + that: List[our_types.SpecialItem] +) -> List[MutableJsonable]: + """ + Serialize :paramref:`that` as a list of + :py:class:`.types.SpecialItem`. + + :param that: list to be serialized + :return: JSON-able representation of :paramref:`that` + """ + jsonable = [] # type: List[MutableJsonable] + for i, item in enumerate(that): + try: + jsonable.append( + _special_item_to_jsonable( + item + ) + ) + except SerializationException as exception: + exception._prepend_index(i) + raise + return jsonable + + +def _some_item_to_jsonable( + that: our_types.SomeItem +) -> MutableMapping[str, MutableJsonable]: + """Serialize :paramref:`that` to a JSON-able representation.""" + jsonable: MutableMapping[str, MutableJsonable] = dict() + try: + jsonable['name'] = that.name + except SerializationException as exception: + exception._prepend_property('name') + raise + if that.nickname is not None: + try: + jsonable['nickname'] = that.nickname + except SerializationException as exception: + exception._prepend_property('nickname') + raise + jsonable['modelType'] = 'SomeItem' + return jsonable + + +def _special_item_to_jsonable( + that: our_types.SpecialItem +) -> MutableMapping[str, MutableJsonable]: + """Serialize :paramref:`that` to a JSON-able representation.""" + jsonable: MutableMapping[str, MutableJsonable] = dict() + try: + jsonable['name'] = that.name + except SerializationException as exception: + exception._prepend_property('name') + raise + if that.nickname is not None: + try: + jsonable['nickname'] = that.nickname + except SerializationException as exception: + exception._prepend_property('nickname') + raise + try: + jsonable['level'] = _int_to_jsonable( + that.level + ) + except SerializationException as exception: + exception._prepend_property('level') + raise + jsonable['modelType'] = 'SpecialItem' + return jsonable + + +def _container_to_jsonable( + that: our_types.Container +) -> MutableMapping[str, MutableJsonable]: + """Serialize :paramref:`that` to a JSON-able representation.""" + jsonable: MutableMapping[str, MutableJsonable] = dict() + try: + jsonable['items'] = _list_of__item_to_jsonable( + that.items + ) + except SerializationException as exception: + exception._prepend_property('items') + raise + try: + jsonable['someItems'] = _list_of__some_item_to_jsonable( + that.some_items + ) + except SerializationException as exception: + exception._prepend_property('some_items') + raise + if that.special_items is not None: + try: + jsonable['specialItems'] = _list_of__special_item_to_jsonable( + that.special_items + ) + except SerializationException as exception: + exception._prepend_property('special_items') + raise + return jsonable + + +def _wrapper_to_jsonable( + that: our_types.Wrapper +) -> MutableMapping[str, MutableJsonable]: + """Serialize :paramref:`that` to a JSON-able representation.""" + jsonable: MutableMapping[str, MutableJsonable] = dict() + try: + jsonable['container'] = _container_to_jsonable( + that.container + ) + except SerializationException as exception: + exception._prepend_property('container') + raise + return jsonable + + +class _Serializer( + our_types.AbstractTransformer[MutableJsonable] +): + """ + Dispatch on the class of an instance to serialize it. + + The methods *are* the serializers, instead of forwarding to them, so that + a dispatch costs a single call. Wherever the class of a value is already + known -- which is every class without concrete descendants -- the serializer + is called directly and this transformer is not involved at all. + """ + + transform_some_item = staticmethod( + _some_item_to_jsonable + ) + transform_special_item = staticmethod( + _special_item_to_jsonable + ) + transform_container = staticmethod( + _container_to_jsonable + ) + transform_wrapper = staticmethod( + _wrapper_to_jsonable + ) + + +_SERIALIZER = _Serializer() + + +def to_jsonable(that: our_types.Class) -> MutableJsonable: + """ + Convert :paramref:`that` to a JSON-able structure. + + :param that: + Model instance to be recursively converted to a JSON-able structure + :return: + JSON-able structure which can be further encoded with, *e.g.*, :py:mod:`json` + :raise: + :py:class:`SerializationException` if :paramref:`that` contains a number + which JSON can not represent + """ + return that.transform(_SERIALIZER) + + +# endregion + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/reporting.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/reporting.py new file mode 100644 index 000000000..c36fbc039 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/reporting.py @@ -0,0 +1,191 @@ +""" +Report where in the data an error lies. + +A path is a sequence of segments which lead from the instance you handed over +down to the erroneous value. It renders as a Python access expression, so that +you can paste it as it stands: + +.. code-block:: + + .things[1].labels[0] + +The vocabulary lives on its own so that +:py:mod:`dummy.verification`, and the verification of +a JSON-able value where the meta-model has one, report with the very same +classes. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import collections +import json +import sys +from typing import ( + Any, + Deque, + Mapping, + Sequence, + Union +) + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +import dummy.types as our_types + + +# NOTE (mristin): +# The SDK defines three of these path vocabularies: this one, the one in +# :py:mod:`dummy.jsonization`, and the one in +# :py:mod:`dummy.xmlcommon`. +# They look alike, and it is tempting to merge them, but they are not +# interchangeable. +# +# Each of them points into a different thing, and every segment carries +# a back-pointer to the container it stepped through, so the type of that +# back-pointer differs from one to the next: +# +# * This one points into the instances which you built, so a property +# segment holds a class of the meta-model. +# * The jsonization's points into the JSON-able structure being read or +# written, so a property segment holds the JSON-able mapping instead: +# while a document is being parsed, the instance which the property would +# belong to does not exist yet. +# * The xmlcommon's points into the XML document, where there are no +# properties at all, only elements, so it names an element instead. +# +# The three also render differently: a path into the instances is a Python +# access expression, a path into a JSON-able structure starts at the root of +# the document and carries no leading dot, and a path into an XML document +# is a relative XPath. +# +# Merging them would mean either dropping the back-pointers, which have been +# part of the public API of this SDK since before these modules were split +# apart, or defining a single path over the union of all the segment kinds -- +# in which case every consumer would have to handle segments which can never +# occur in its world. Three small vocabularies which each say exactly what +# they can say cost less than one large one which lies about its range. + + +class PropertySegment: + """Represent a property access on a path to an erroneous value.""" + + #: Instance containing the property + instance: Final[our_types.Class] + + #: Name of the property + name: Final[str] + + def __init__( + self, + instance: our_types.Class, + name: str + ) -> None: + """Initialize with the given values.""" + self.instance = instance + self.name = name + + def __str__(self) -> str: + return f'.{self.name}' + + +class IndexSegment: + """Represent an index access on a path to an erroneous value.""" + + #: Sequence containing the item at :py:attr:`~index` + sequence: Final[Sequence[Any]] + + #: Index of the item + index: Final[int] + + def __init__( + self, + sequence: Sequence[Any], + index: int + ) -> None: + """Initialize with the given values.""" + self.sequence = sequence + self.index = index + + def __str__(self) -> str: + return f'[{self.index}]' + + +class KeySegment: + """ + Represent a member access on a path to an erroneous value. + + Unlike a :py:class:`PropertySegment`, which names a property of one of our + classes, a key names a member of an open JSON-able object. It is known only + at run time, and can be any string at all, so it is always rendered + as a subscript. + """ + + #: Mapping containing the value at :py:attr:`~key` + mapping: Final[Mapping[str, Any]] + + #: Key of the value + key: Final[str] + + def __init__( + self, + mapping: Mapping[str, Any], + key: str + ) -> None: + """Initialize with the given values.""" + self.mapping = mapping + self.key = key + + def __str__(self) -> str: + return f'[{json.dumps(self.key, ensure_ascii=False)}]' + + +Segment = Union[PropertySegment, IndexSegment, KeySegment] + + +class Path: + """Represent the relative path to the erroneous value.""" + + def __init__(self) -> None: + """Initialize as an empty path.""" + # NOTE (mristin): + # A path is built as the stack unwinds, so every segment is prepended and + # none is ever appended. A list would copy the whole path on each of them, + # which makes a path of depth *d* cost *d^2* to build, while a deque + # prepends in constant time. + self._segments = collections.deque() # type: Deque[Segment] + + @property + def segments(self) -> Sequence[Segment]: + """Get the segments of the path.""" + return self._segments + + def _prepend(self, segment: Segment) -> None: + """Insert the :paramref:`segment` in front of other segments.""" + self._segments.appendleft(segment) + + def __str__(self) -> str: + return "".join(str(segment) for segment in self._segments) + + +class Error: + """Represent a verification error in the data.""" + + #: Human-readable description of the error + cause: Final[str] + + #: Path to the erroneous value + path: Final[Path] + + def __init__(self, cause: str) -> None: + """Initialize as an error with an empty path.""" + self.cause = cause + self.path = Path() + + def __repr__(self) -> str: + return f"Error(path={self.path}, cause={self.cause})" diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/stringification.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/stringification.py new file mode 100644 index 000000000..d7499e6a4 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/stringification.py @@ -0,0 +1,17 @@ +"""De-serialize enumerations from string representations.""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +from typing import ( + Mapping, + Optional, +) + +import dummy.types as our_types + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/types.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/types.py new file mode 100644 index 000000000..148cc254b --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/types.py @@ -0,0 +1,905 @@ +""" +Test the variance of the containers passed as arguments. + +A read-only ``Sequence[...]`` argument of a verification function is generic if its +items are instances of a class with descendants, so that the sequences of +the descendants can be passed to it without a copy. The arguments of the methods +and the mutable ``List[...]`` arguments remain invariant. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import abc +import enum +from typing import ( + Generic, + Iterator, + Optional, + TypeVar, + List, + Tuple, + Sequence +) + + +T = TypeVar("T") +ContextT = TypeVar("ContextT") + + +class Class(abc.ABC): + """Represent the most general class of the meta-model.""" + @abc.abstractmethod + def descend_once(self) -> Iterator["Class"]: + """Iterate over all the instances referenced from this one.""" + raise NotImplementedError() + + @abc.abstractmethod + def descend(self) -> Iterator["Class"]: + """Iterate recursively over all the instances referenced from this one.""" + raise NotImplementedError() + + @abc.abstractmethod + def accept( + self, + visitor: "AbstractVisitor" + ) -> None: + """ + Dispatch the :paramref:`visitor` on this instance. + + :param visitor: to be dispatched + """ + raise NotImplementedError() + + @abc.abstractmethod + def accept_with_context( + self, + visitor: "AbstractVisitorWithContext[ContextT]", + context: ContextT + ) -> None: + """ + Dispatch the :paramref:`visitor` on this instance with :paramref:`context`. + + :param visitor: to be dispatched + :param context: of the visitation + """ + raise NotImplementedError() + + @abc.abstractmethod + def transform( + self, + transformer: "AbstractTransformer[T]" + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance. + + :param transformer: to be dispatched + :return: transformed self + """ + raise NotImplementedError() + + @abc.abstractmethod + def transform_with_context( + self, + transformer: "AbstractTransformerWithContext[ContextT, T]", + context: ContextT + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance with :paramref:`context`. + + :param transformer: to be dispatched + :return: transformed self + """ + raise NotImplementedError() + + +# pylint: disable=redefined-builtin + + +class Item(Class): + # pylint: disable=missing-class-docstring + + name: str + + nickname: Optional[str] + + def __init__( + self, + name: str, + nickname: Optional[str] = None + ) -> None: + """Initialize with the given values.""" + self.name = name + self.nickname = nickname + + +class SomeItem(Item): + # pylint: disable=missing-class-docstring + + def descend_once(self) -> Iterator[Class]: + """ + Iterate over the instances referenced from this instance. + + We do not recurse into the referenced instance. + + :yield: instances directly referenced from this instance + """ + return self._descend(recurse=False) + + def descend(self) -> Iterator[Class]: + """ + Iterate recursively over the instances referenced from this one. + + :yield: instances recursively referenced from this instance + """ + return self._descend(recurse=True) + + def _descend(self, recurse: bool) -> Iterator[Class]: + """ + Iterate over the instances referenced from this one, and recursively + over their descendants if :paramref:`recurse` is set. + + :param recurse: if set, descend recursively into the referenced instances + :yield: instances referenced from this instance + """ + # No descendable properties + return + # For this uncommon return-yield construction, see: + # https://stackoverflow.com/questions/13243766/how-to-define-an-empty-generator-function + # noinspection PyUnreachableCode + yield + + def accept(self, visitor: "AbstractVisitor") -> None: + """Dispatch the :paramref:`visitor` on this instance.""" + visitor.visit_some_item(self) + + def accept_with_context( + self, + visitor: "AbstractVisitorWithContext[ContextT]", + context: ContextT + ) -> None: + """Dispatch the :paramref:`visitor` on this instance in :paramref:`context`.""" + visitor.visit_some_item_with_context(self, context) + + def transform( + self, + transformer: "AbstractTransformer[T]" + ) -> T: + """Dispatch the :paramref:`transformer` on this instance.""" + return transformer.transform_some_item(self) + + def transform_with_context( + self, + transformer: "AbstractTransformerWithContext[ContextT, T]", + context: ContextT + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance in :paramref:`context`. + """ + return transformer.transform_some_item_with_context( + self, context) + + def __init__( + self, + name: str, + nickname: Optional[str] = None + ) -> None: + """Initialize with the given values.""" + Item.__init__( + self, + name, + nickname + ) + + +class SpecialItem(SomeItem): + # pylint: disable=missing-class-docstring + + level: int + + def descend_once(self) -> Iterator[Class]: + """ + Iterate over the instances referenced from this instance. + + We do not recurse into the referenced instance. + + :yield: instances directly referenced from this instance + """ + return self._descend(recurse=False) + + def descend(self) -> Iterator[Class]: + """ + Iterate recursively over the instances referenced from this one. + + :yield: instances recursively referenced from this instance + """ + return self._descend(recurse=True) + + def _descend(self, recurse: bool) -> Iterator[Class]: + """ + Iterate over the instances referenced from this one, and recursively + over their descendants if :paramref:`recurse` is set. + + :param recurse: if set, descend recursively into the referenced instances + :yield: instances referenced from this instance + """ + # No descendable properties + return + # For this uncommon return-yield construction, see: + # https://stackoverflow.com/questions/13243766/how-to-define-an-empty-generator-function + # noinspection PyUnreachableCode + yield + + def accept(self, visitor: "AbstractVisitor") -> None: + """Dispatch the :paramref:`visitor` on this instance.""" + visitor.visit_special_item(self) + + def accept_with_context( + self, + visitor: "AbstractVisitorWithContext[ContextT]", + context: ContextT + ) -> None: + """Dispatch the :paramref:`visitor` on this instance in :paramref:`context`.""" + visitor.visit_special_item_with_context(self, context) + + def transform( + self, + transformer: "AbstractTransformer[T]" + ) -> T: + """Dispatch the :paramref:`transformer` on this instance.""" + return transformer.transform_special_item(self) + + def transform_with_context( + self, + transformer: "AbstractTransformerWithContext[ContextT, T]", + context: ContextT + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance in :paramref:`context`. + """ + return transformer.transform_special_item_with_context( + self, context) + + def __init__( + self, + name: str, + level: int, + nickname: Optional[str] = None + ) -> None: + """Initialize with the given values.""" + SomeItem.__init__( + self, + name, + nickname + ) + self.level = level + + +class Container(Class): + # pylint: disable=missing-class-docstring + + items: List['Item'] + + some_items: List['SomeItem'] + + special_items: Optional[List['SpecialItem']] + + def over_special_items_or_empty( + self + ) -> Iterator['SpecialItem']: + """Yield from :py:attr:`.special_items` if set.""" + if self.special_items is not None: + yield from self.special_items + + def count_items( + self, + items: Sequence['Item'] + ) -> int: + """ + Count the :paramref:`items`. + + The arguments of the methods are never generic, so only the sequences of + :py:class:`Item` can be passed to it, but not the sequences of + the descendants. + """ + # pylint: disable=all + return len(items) + + def descend_once(self) -> Iterator[Class]: + """ + Iterate over the instances referenced from this instance. + + We do not recurse into the referenced instance. + + :yield: instances directly referenced from this instance + """ + return self._descend(recurse=False) + + def descend(self) -> Iterator[Class]: + """ + Iterate recursively over the instances referenced from this one. + + :yield: instances recursively referenced from this instance + """ + return self._descend(recurse=True) + + def _descend(self, recurse: bool) -> Iterator[Class]: + """ + Iterate over the instances referenced from this one, and recursively + over their descendants if :paramref:`recurse` is set. + + :param recurse: if set, descend recursively into the referenced instances + :yield: instances referenced from this instance + """ + yield from _descend_list_of__item(self.items, recurse) + + yield from _descend_list_of__some_item(self.some_items, recurse) + + if self.special_items is not None: + yield from _descend_list_of__special_item(self.special_items, recurse) + + def accept(self, visitor: "AbstractVisitor") -> None: + """Dispatch the :paramref:`visitor` on this instance.""" + visitor.visit_container(self) + + def accept_with_context( + self, + visitor: "AbstractVisitorWithContext[ContextT]", + context: ContextT + ) -> None: + """Dispatch the :paramref:`visitor` on this instance in :paramref:`context`.""" + visitor.visit_container_with_context(self, context) + + def transform( + self, + transformer: "AbstractTransformer[T]" + ) -> T: + """Dispatch the :paramref:`transformer` on this instance.""" + return transformer.transform_container(self) + + def transform_with_context( + self, + transformer: "AbstractTransformerWithContext[ContextT, T]", + context: ContextT + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance in :paramref:`context`. + """ + return transformer.transform_container_with_context( + self, context) + + def __init__( + self, + items: List['Item'], + some_items: List['SomeItem'], + special_items: Optional[List['SpecialItem']] = None + ) -> None: + """Initialize with the given values.""" + self.items = items + self.some_items = some_items + self.special_items = special_items + + +class Wrapper(Class): + # pylint: disable=missing-class-docstring + + container: 'Container' + + def descend_once(self) -> Iterator[Class]: + """ + Iterate over the instances referenced from this instance. + + We do not recurse into the referenced instance. + + :yield: instances directly referenced from this instance + """ + return self._descend(recurse=False) + + def descend(self) -> Iterator[Class]: + """ + Iterate recursively over the instances referenced from this one. + + :yield: instances recursively referenced from this instance + """ + return self._descend(recurse=True) + + def _descend(self, recurse: bool) -> Iterator[Class]: + """ + Iterate over the instances referenced from this one, and recursively + over their descendants if :paramref:`recurse` is set. + + :param recurse: if set, descend recursively into the referenced instances + :yield: instances referenced from this instance + """ + yield self.container + + if recurse: + yield from self.container.descend() + + def accept(self, visitor: "AbstractVisitor") -> None: + """Dispatch the :paramref:`visitor` on this instance.""" + visitor.visit_wrapper(self) + + def accept_with_context( + self, + visitor: "AbstractVisitorWithContext[ContextT]", + context: ContextT + ) -> None: + """Dispatch the :paramref:`visitor` on this instance in :paramref:`context`.""" + visitor.visit_wrapper_with_context(self, context) + + def transform( + self, + transformer: "AbstractTransformer[T]" + ) -> T: + """Dispatch the :paramref:`transformer` on this instance.""" + return transformer.transform_wrapper(self) + + def transform_with_context( + self, + transformer: "AbstractTransformerWithContext[ContextT, T]", + context: ContextT + ) -> T: + """ + Dispatch the :paramref:`transformer` on this instance in :paramref:`context`. + """ + return transformer.transform_wrapper_with_context( + self, context) + + def __init__(self, container: 'Container') -> None: + """Initialize with the given values.""" + self.container = container + + +def _descend_list_of__item( + that: List['Item'], + recurse: bool +) -> Iterator[Class]: + """ + Iterate over the instances held by :paramref:`that`. + + If :paramref:`recurse` is set, descend recursively into the instances + as well. + """ + if not recurse: + yield from that + return + + for item in that: + yield item + + yield from item.descend() + + +def _descend_list_of__some_item( + that: List['SomeItem'], + recurse: bool +) -> Iterator[Class]: + """ + Iterate over the instances held by :paramref:`that`. + + If :paramref:`recurse` is set, descend recursively into the instances + as well. + """ + if not recurse: + yield from that + return + + for item in that: + yield item + + yield from item.descend() + + +def _descend_list_of__special_item( + that: List['SpecialItem'], + recurse: bool +) -> Iterator[Class]: + """ + Iterate over the instances held by :paramref:`that`. + + If :paramref:`recurse` is set, descend recursively into the instances + as well. + """ + if not recurse: + yield from that + return + + for item in that: + yield item + + yield from item.descend() + + +class AbstractVisitor: + """Visit the instances of the model.""" + def visit( + self, + that: Class + ) -> None: + """Double-dispatch on :paramref:`that`.""" + that.accept(self) + + @abc.abstractmethod + def visit_some_item( + self, + that: SomeItem + ) -> None: + """Visit :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_special_item( + self, + that: SpecialItem + ) -> None: + """Visit :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_container( + self, + that: Container + ) -> None: + """Visit :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_wrapper( + self, + that: Wrapper + ) -> None: + """Visit :paramref:`that`.""" + raise NotImplementedError() + + +class AbstractVisitorWithContext(Generic[ContextT]): + """Visit the instances of the model with context.""" + def visit_with_context( + self, + that: Class, + context: ContextT + ) -> None: + """Double-dispatch on :paramref:`that`.""" + that.accept_with_context(self, context) + + @abc.abstractmethod + def visit_some_item_with_context( + self, + that: SomeItem, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_special_item_with_context( + self, + that: SpecialItem, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_container_with_context( + self, + that: Container, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def visit_wrapper_with_context( + self, + that: Wrapper, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + +class PassThroughVisitor(AbstractVisitor): + """ + Visit the instances of the model without action. + + This visitor is not meant to be directly used. Instead, you usually + inherit from it, and implement only the relevant visit methods. + """ + def visit( + self, + that: Class + ) -> None: + """Double-dispatch on :paramref:`that`.""" + that.accept(self) + + def visit_some_item( + self, + that: SomeItem + ) -> None: + """Visit :paramref:`that`.""" + for another in that.descend_once(): + self.visit(another) + + def visit_special_item( + self, + that: SpecialItem + ) -> None: + """Visit :paramref:`that`.""" + for another in that.descend_once(): + self.visit(another) + + def visit_container( + self, + that: Container + ) -> None: + """Visit :paramref:`that`.""" + for another in that.descend_once(): + self.visit(another) + + def visit_wrapper( + self, + that: Wrapper + ) -> None: + """Visit :paramref:`that`.""" + for another in that.descend_once(): + self.visit(another) + + +class PassThroughVisitorWithContext( + AbstractVisitorWithContext[ContextT] +): + """ + Visit the instances of the model without action and in context. + + This visitor is not meant to be directly used. Instead, you usually + inherit from it, and implement only the relevant visit methods. + """ + def visit_with_context( + self, + that: Class, + context: ContextT + ) -> None: + """Double-dispatch on :paramref:`that`.""" + that.accept_with_context(self, context) + + def visit_some_item_with_context( + self, + that: SomeItem, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + for another in that.descend_once(): + self.visit_with_context(another, context) + + def visit_special_item_with_context( + self, + that: SpecialItem, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + for another in that.descend_once(): + self.visit_with_context(another, context) + + def visit_container_with_context( + self, + that: Container, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + for another in that.descend_once(): + self.visit_with_context(another, context) + + def visit_wrapper_with_context( + self, + that: Wrapper, + context: ContextT + ) -> None: + """Visit :paramref:`that` in :paramref:`context`.""" + for another in that.descend_once(): + self.visit_with_context(another, context) + + +class AbstractTransformer(Generic[T]): + """Transform the instances of the model.""" + def transform( + self, + that: Class + ) -> T: + """Double-dispatch on :paramref:`that`.""" + return that.transform(self) + + @abc.abstractmethod + def transform_some_item( + self, + that: SomeItem + ) -> T: + """Transform :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_special_item( + self, + that: SpecialItem + ) -> T: + """Transform :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_container( + self, + that: Container + ) -> T: + """Transform :paramref:`that`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_wrapper( + self, + that: Wrapper + ) -> T: + """Transform :paramref:`that`.""" + raise NotImplementedError() + + +class AbstractTransformerWithContext( + Generic[ContextT, T] +): + """Transform the instances of the model in context.""" + def transform_with_context( + self, + that: Class, + context: ContextT + ) -> T: + """Double-dispatch on :paramref:`that`.""" + return that.transform_with_context(self, context) + + @abc.abstractmethod + def transform_some_item_with_context( + self, + that: SomeItem, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_special_item_with_context( + self, + that: SpecialItem, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_container_with_context( + self, + that: Container, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + @abc.abstractmethod + def transform_wrapper_with_context( + self, + that: Wrapper, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + raise NotImplementedError() + + +class TransformerWithDefault(AbstractTransformer[T]): + """ + Transform the instances of the model. + + If you do not override the transformation methods, they simply + return :py:attr:`.default`. + """ + #: Default value which is returned if no override of the transformation + default: T + + def __init__(self, default: T) -> None: + """Initialize with the given :paramref:`default` value.""" + self.default = default + + def transform( + self, + that: Class + ) -> T: + """Double-dispatch on :paramref:`that`.""" + return that.transform(self) + + def transform_some_item( + self, + that: SomeItem + ) -> T: + """Transform :paramref:`that`.""" + return self.default + + def transform_special_item( + self, + that: SpecialItem + ) -> T: + """Transform :paramref:`that`.""" + return self.default + + def transform_container( + self, + that: Container + ) -> T: + """Transform :paramref:`that`.""" + return self.default + + def transform_wrapper( + self, + that: Wrapper + ) -> T: + """Transform :paramref:`that`.""" + return self.default + + +class TransformerWithDefaultAndContext( + AbstractTransformerWithContext[ContextT, T] +): + """ + Transform the instances of the model in context. + + If you do not override the transformation methods, they simply + return :py:attr:`.default`. + """ + #: Default value which is returned if no override of the transformation + default: T + + def __init__(self, default: T) -> None: + """Initialize with the given :paramref:`default` value.""" + self.default = default + + def transform_with_context( + self, + that: Class, + context: ContextT + ) -> T: + """Double-dispatch on :paramref:`that`.""" + return that.transform_with_context(self, context) + + def transform_some_item_with_context( + self, + that: SomeItem, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + return self.default + + def transform_special_item_with_context( + self, + that: SpecialItem, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + return self.default + + def transform_container_with_context( + self, + that: Container, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + return self.default + + def transform_wrapper_with_context( + self, + that: Wrapper, + context: ContextT + ) -> T: + """Transform :paramref:`that` in :paramref:`context`.""" + return self.default + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/verification.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/verification.py new file mode 100644 index 000000000..762fc85a5 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/verification.py @@ -0,0 +1,617 @@ +""" +Verify that the instances of the meta-model satisfy the invariants. + +Here is an example how to verify an instance of :py:class:`dummy.types.SomeItem`: + +.. code-block:: + + import dummy.types as our_types + import dummy.verification as our_verification + + an_instance = our_types.SomeItem( + # ... some constructor arguments ... + ) + + for error in our_verification.verify(an_instance): + print(f"{error.cause} at: {error.path}") +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import collections.abc +import math +import re +import struct +import sys +from typing import ( + Any, + Callable, + Iterable, + Iterator, + List, + Mapping, + Optional, + Pattern, + Sequence, + Set, + Tuple, + Union +) + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +from dummy import ( + constants as our_constants, + reporting as our_reporting, + types as our_types, +) + + +PropertySegment = our_reporting.PropertySegment +IndexSegment = our_reporting.IndexSegment +KeySegment = our_reporting.KeySegment +Segment = our_reporting.Segment +Path = our_reporting.Path + +Error = our_reporting.Error + + +def names_are_unique( + items: Sequence[our_types.Item] +) -> bool: + """ + Check that the names of the :paramref:`items` are unique. + + The :paramref:`items` are a generic sequence, so the sequences of + the descendants of :py:class:`.types.Item` can be passed without a copy. + """ + # pylint: disable=all + name_set: Set[str] = set() + for item in items: + if item.name in name_set: + return False + name_set.add(item.name) + return True + + +def names_are_non_empty( + items: Sequence[our_types.Item] +) -> bool: + """Iterate over a generic sequence in a generator.""" + # pylint: disable=all + return ( + all( + len(item.name) > 0 + for item in items + )) + + +def some_special_item_has_level( + items: Sequence[our_types.Item] +) -> bool: + """Narrow the items of a generic sequence in a generator.""" + # pylint: disable=all + return ( + any( + ( + isinstance(item, our_types.SpecialItem) + and item.level > 0 + ) + for item in items + )) + + +def first_is_special( + items: Sequence[our_types.Item] +) -> bool: + """Index a generic sequence, and narrow the item.""" + # pylint: disable=all + if len(items) == 0: + return False + first = items[0] + if isinstance(first, our_types.SpecialItem): + return first.level > 0 + return False + + +def nicknames_are_unique( + items: Sequence[our_types.Item] +) -> bool: + """ + Check that the nicknames of the :paramref:`items` are unique, if specified. + + This mirrors the body of ``ID_shorts_are_unique`` in aas-core-meta V3.2: + a local variable takes an optional property of an item of a generic sequence, + and the loop continues if it is None. + """ + # pylint: disable=all + nickname_set: Set[str] = set() + for item in items: + nickname = item.nickname + if nickname is None: + continue + if nickname in nickname_set: + return False + nickname_set.add(nickname) + return True + + +def levels_of_special_items_are_positive( + items: Sequence[our_types.Item] +) -> bool: + """ + Narrow the loop variable over a generic sequence in a for-loop. + + The loop variable is of the type parameter in Go, so the down-cast needs to + go through ``any(...)``, and of the template parameter in C++. + """ + # pylint: disable=all + for item in items: + if isinstance(item, our_types.SpecialItem): + if item.level <= 0: + return False + return True + + +def last_is_special( + items: Sequence[our_types.Item] +) -> bool: + """ + Narrow an immutable local variable which holds an item of a generic sequence. + + In contrast to :py:func:`first_is_special`, the local variable is annotated, so + it is of the parent class instead of the type parameter. Hence, Go down-casts + it without ``any(...)``, and C++ binds the item to a constant reference to + the shared pointer of the parent class. + """ + # pylint: disable=all + if len(items) == 0: + return False + last: Final[our_types.Item] = items[len(items) - 1] + if isinstance(last, our_types.SpecialItem): + return last.level > 0 + return False + + +def names_are_disjoint( + items: Sequence[our_types.Item], + other_items: Sequence[our_types.SomeItem], + shared_name: str +) -> bool: + """ + Mix two generic sequences with an argument which is not generic. + + The names must differ, except for the :paramref:`shared_name`, which may repeat. + + Go declares two type parameters, and C++ two template parameters. The nested + generators iterate over two different generic sequences, so their variables + are of two different type parameters. + """ + # pylint: disable=all + return ( + all( + all( + ( + item.name != other_item.name + or item.name == shared_name + ) + for other_item in other_items + ) + for item in items + )) + + +def aliased_names_are_unique( + items: Sequence[our_types.Item], + optional_items: Optional[Sequence[our_types.Item]] +) -> bool: + """ + Use the immutable aliases of the generic sequences. + + An immutable alias of the very same type shares the type annotation of + the aliased generic sequence, so it is a generic sequence itself. Go declares + the alias with the type parameter, C++ with the template parameter, while C# + and Java infer its type with ``var``. The ``alias_of_alias`` aliases an alias, + and the ``optional_alias`` aliases an optional generic sequence. + """ + # pylint: disable=all + alias: Final[Sequence[our_types.Item]] = items + alias_of_alias: Final[Sequence[our_types.Item]] = alias + optional_alias: Final[Optional[Sequence[our_types.Item]]] = optional_items + for item in alias: + if isinstance(item, our_types.SpecialItem): + if item.level <= 0: + return False + return ( + ( + ( + all( + len(item.name) > 0 + for item in alias_of_alias + ) + ) + and names_are_unique(alias_of_alias) + and optional_names_are_unique(optional_alias) + and ( + ( + (optional_alias is None) + or names_are_unique(optional_alias) + ) + ) + )) + + +def contains_some_item( + some_item: our_types.SomeItem, + items: Sequence[our_types.Item] +) -> bool: + """ + Look up an instance of a descendant in a generic sequence. + + Go compares the instances as ``any``'s, as the items are of the type parameter, + and C++ converts the member to the shared pointer of :py:class:`.types.Item` so that + the raw pointers are comparable for any template parameter. + """ + # pylint: disable=all + return some_item in items + + +def items_overlap( + items: Sequence[our_types.Item], + other_items: Sequence[our_types.SomeItem] +) -> bool: + """ + Look up the items of a generic sequence in another generic sequence. + + The member and the items are of two different type parameters. + """ + # pylint: disable=all + return ( + any( + other_item in items + for other_item in other_items + )) + + +def contains_special_item( + special_item: our_types.SpecialItem, + items: Sequence[our_types.SpecialItem] +) -> bool: + """ + Look up an instance in a sequence which is not generic. + + The :py:class:`.types.SpecialItem` has no descendants, so the :paramref:`items` are not + a generic sequence, but Go still compares the instances as ``any``'s. + """ + # pylint: disable=all + return special_item in items + + +def container_names_are_unique( + container: our_types.Container +) -> bool: + """ + Pass the local variables on to generic sequences. + + The ``some_items`` is a read-only alias without an annotation, while + the ``all_some_items`` is an immutable local variable declared with + ``Final[...]``. Both hold the sequences of :py:class:`.types.SomeItem`, and are passed + on to the generic sequences of :py:class:`.types.Item`, required as well as optional. + """ + # pylint: disable=all + some_items = container.some_items + all_some_items: Final[Sequence[our_types.SomeItem]] = container.some_items + return ( + ( + names_are_unique(some_items) + and names_are_unique(all_some_items) + and optional_names_are_unique(all_some_items) + )) + + +def optional_names_are_unique( + items: Optional[Sequence[our_types.Item]] +) -> bool: + """Pass an optional generic sequence on to a generic sequence.""" + # pylint: disable=all + return ( + ( + (items is None) + or names_are_unique(items) + )) + + +def some_names_are_unique( + some_items: Sequence[our_types.SomeItem], + special_items: Optional[Sequence[our_types.SpecialItem]] +) -> bool: + """ + Pass the generic sequences of the descendants on. + + The :paramref:`some_items` are passed on as a required and as an optional + sequence of the parent class, while the :paramref:`special_items` are passed on + as an optional sequence and, once narrowed, as a required sequence of the parent + class of the parent class. + """ + # pylint: disable=all + return ( + ( + names_are_unique(some_items) + and optional_names_are_unique(some_items) + and optional_names_are_unique(special_items) + and optional_names_are_unique(None) + and ( + ( + (special_items is None) + or names_are_unique(special_items) + ) + ) + )) + + +def levels_are_positive( + items: Sequence[our_types.SpecialItem] +) -> bool: + """ + Check that the levels are positive. + + The :py:class:`.types.SpecialItem` has no descendants, so the :paramref:`items` are not + a generic sequence. + """ + # pylint: disable=all + return ( + all( + item.level > 0 + for item in items + )) + + +class _Transformer( + our_types.AbstractTransformer[ + Iterator[Error] + ] +): + # noinspection PyMethodMayBeStatic + def transform_some_item( + self, + that: our_types.SomeItem + ) -> Iterator[Error]: + # No verification has been defined for SomeItem. + return + # For this uncommon return-yield construction, see: + # https://stackoverflow.com/questions/13243766/how-to-define-an-empty-generator-function + # noinspection PyUnreachableCode + yield + + # noinspection PyMethodMayBeStatic + def transform_special_item( + self, + that: our_types.SpecialItem + ) -> Iterator[Error]: + # No verification has been defined for SpecialItem. + return + # For this uncommon return-yield construction, see: + # https://stackoverflow.com/questions/13243766/how-to-define-an-empty-generator-function + # noinspection PyUnreachableCode + yield + + # noinspection PyMethodMayBeStatic + def transform_container( + self, + that: our_types.Container + ) -> Iterator[Error]: + if not ( + not (that.special_items is not None) + or ( + all( + contains_special_item( + special_item, + that.special_items + ) + for special_item in that.special_items + ) + ) + ): + yield Error( + 'Each special item must be among the special items.' + ) + + if not ( + ( + len(that.some_items) == 0 + or items_overlap(that.items, that.some_items) + ) + ): + yield Error( + 'If there are some items, they must overlap with the items.' + ) + + if not ( + all( + contains_some_item(some_item, that.items) + for some_item in that.some_items + ) + ): + yield Error( + 'Some items must be among the items.' + ) + + if not ( + aliased_names_are_unique( + that.some_items, + that.special_items + ) + ): + yield Error( + 'Names of some items and of special items must be unique ' + + 'through aliases.' + ) + + if not ( + names_are_disjoint(that.items, that.some_items, 'x') + ): + yield Error( + 'Names of the items and of some items must be disjoint.' + ) + + if not ( + not some_special_item_has_level(that.items) + or last_is_special(that.items) + ): + yield Error( + 'If there is a special item with level, the last item must ' + + 'be special.' + ) + + if not levels_of_special_items_are_positive(that.items): + yield Error( + 'Levels of the special items among the items must be ' + + 'positive.' + ) + + if not nicknames_are_unique(that.some_items): + yield Error( + 'Nicknames of some items must be unique.' + ) + + if not (that.count_items(that.items) == len(that.items)): + yield Error( + 'Count of the items must match the number of the items.' + ) + + if not optional_names_are_unique(that.special_items): + yield Error( + 'Names of the special items must be unique.' + ) + + if not ( + not (that.special_items is not None) + or first_is_special(that.special_items) + ): + yield Error( + 'The first special item must be special.' + ) + + if not ( + not (that.special_items is not None) + or levels_are_positive(that.special_items) + ): + yield Error( + 'Levels of the special items must be positive.' + ) + + if not ( + not some_special_item_has_level(that.items) + or first_is_special(that.items) + ): + yield Error( + 'If there is a special item with level, the first item must ' + + 'be special.' + ) + + if not names_are_non_empty(that.some_items): + yield Error( + 'Names of some items must be non-empty.' + ) + + if not ( + some_names_are_unique( + that.some_items, + that.special_items + ) + ): + yield Error( + 'Names of some items and of special items must be unique.' + ) + + if not names_are_unique(that.items): + yield Error( + 'Names of the items must be unique.' + ) + + for error in _verify_list_of__class(that.items): + error.path._prepend( + PropertySegment( + that, + 'items' + ) + ) + yield error + + for error in _verify_list_of__class(that.some_items): + error.path._prepend( + PropertySegment( + that, + 'some_items' + ) + ) + yield error + + if that.special_items is not None: + for error in _verify_list_of__class(that.special_items): + error.path._prepend( + PropertySegment( + that, + 'special_items' + ) + ) + yield error + + # noinspection PyMethodMayBeStatic + def transform_wrapper( + self, + that: our_types.Wrapper + ) -> Iterator[Error]: + if not container_names_are_unique(that.container): + yield Error( + 'Names of some items of the container must be unique.' + ) + + for error in verify(that.container): + error.path._prepend( + PropertySegment( + that, + 'container' + ) + ) + yield error + + +_TRANSFORMER = _Transformer() + + +def verify( + that: our_types.Class +) -> Iterator[Error]: + """ + Verify the constraints of :paramref:`that` recursively. + + :param that: instance whose constraints we want to verify + :yield: constraint violations + """ + yield from _TRANSFORMER.transform(that) + + +def _verify_list_of__class( + that: Sequence[our_types.Class] +) -> Iterator[Error]: + """Verify the items of :paramref:`that` recursively.""" + for i, item in enumerate(that): + for error in verify(item): + error.path._prepend( + IndexSegment( + that, + i + ) + ) + yield error + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlcommon.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlcommon.py new file mode 100644 index 000000000..a1d73d712 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlcommon.py @@ -0,0 +1,759 @@ +""" +Provide the primitives which the modules reading and writing XML share. + +Reading means walking a stream of ``(event, element)`` tuples: checking that +an element resides in the expected namespace, consuming the end element which +corresponds to a start element and taking the text out of an element. Writing +means framing an element. Each of these is defined here exactly once, so that +neither :py:mod:`dummy.xmlization` nor the XML-RPC subset +over which a JSON-able value is de/serialized carries a copy of its own. + +The namespace is a constant of this module, :py:attr:`NAMESPACE`, and no +argument of its primitives. This module is generated for a single meta-model, +and a meta-model prescribes exactly one namespace, so there is nothing for +a caller to choose. +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import json +import re +import sys +from typing import ( + Callable, + Iterator, + List, + Mapping, + Optional, + Sequence, + TextIO, + Tuple, + Union +) + +if sys.version_info >= (3, 8): + from typing import ( + Final, + Protocol + ) +else: + from typing_extensions import ( + Final, + Protocol + ) + + +#: XML namespace in which all the elements of a document are expected to reside +NAMESPACE = 'https://dummy.com' + + +class Element(Protocol): + """Behave like :py:meth:`xml.etree.ElementTree.Element`.""" + + @property + def attrib(self) -> Optional[Mapping[str, str]]: + """Attributes of the element""" + raise NotImplementedError() + + @property + def text(self) -> Optional[str]: + """Text content of the element""" + raise NotImplementedError() + + @property + def tail(self) -> Optional[str]: + """Tail text of the element""" + raise NotImplementedError() + + @property + def tag(self) -> str: + """Tag of the element; with a namespace provided as a ``{...}`` prefix""" + raise NotImplementedError() + + def clear(self) -> None: + """Behave like :py:meth:`xml.etree.ElementTree.Element.clear`.""" + raise NotImplementedError() + + +class HasIterparse(Protocol): + """Parse an XML document incrementally.""" + + # NOTE (mristin): + # ``self`` is not used in this context, but is necessary for Mypy, + # see: https://github.com/python/mypy/issues/5018 and + # https://github.com/python/mypy/commit/3efbc5c5e910296a60ed5b9e0e7eb11dd912c3ed#diff-e165eb7aed9dca0a5ebd93985c8cd263a6462d36ac185f9461348dc5a1396d76R9937 + + def iterparse( + self, + source: TextIO, + events: Optional[Sequence[str]] = None + ) -> Iterator[Tuple[str, Element]]: + """Behave like :py:func:`xml.etree.ElementTree.iterparse`.""" + + +# NOTE (mristin): +# The SDK defines three of these path vocabularies: this one, the one in +# :py:mod:`dummy.jsonization`, and the one in +# :py:mod:`dummy.xmlcommon`. +# They look alike, and it is tempting to merge them, but they are not +# interchangeable. +# +# Each of them points into a different thing, and every segment carries +# a back-pointer to the container it stepped through, so the type of that +# back-pointer differs from one to the next: +# +# * This one points into the instances which you built, so a property +# segment holds a class of the meta-model. +# * The jsonization's points into the JSON-able structure being read or +# written, so a property segment holds the JSON-able mapping instead: +# while a document is being parsed, the instance which the property would +# belong to does not exist yet. +# * The xmlcommon's points into the XML document, where there are no +# properties at all, only elements, so it names an element instead. +# +# The three also render differently: a path into the instances is a Python +# access expression, a path into a JSON-able structure starts at the root of +# the document and carries no leading dot, and a path into an XML document +# is a relative XPath. +# +# Merging them would mean either dropping the back-pointers, which have been +# part of the public API of this SDK since before these modules were split +# apart, or defining a single path over the union of all the segment kinds -- +# in which case every consumer would have to handle segments which can never +# occur in its world. Three small vocabularies which each say exactly what +# they can say cost less than one large one which lies about its range. + + +class ElementSegment: + """Represent an element on a path to the erroneous value.""" + #: Erroneous element + element: Final[Element] + + def __init__( + self, + element: Element + ) -> None: + """Initialize with the given values.""" + self.element = element + + def __str__(self) -> str: + """ + Render the segment as a tag without the namespace. + + We deliberately omit the namespace in the tag names. If you want to actually + query with the resulting XPath, you have to insert the namespaces manually. + We did not know how to include the namespace in a meaningful way, as XPath + assumes namespace prefixes to be defined *outside* of the document. At least + the path thus rendered is informative, and you should be able to descend it + manually. + """ + _, has_namespace, tag_wo_ns = self.element.tag.rpartition('}') + if not has_namespace: + return self.element.tag + else: + return tag_wo_ns + + +class IndexSegment: + """Represent an element in a sequence on a path to the erroneous value.""" + #: Erroneous element + element: Final[Element] + + #: Index of the element in the sequence, starting at zero + index: Final[int] + + def __init__( + self, + element: Element, + index: int + ) -> None: + """Initialize with the given values.""" + self.element = element + self.index = index + + def __str__(self) -> str: + """ + Render the segment as an element wildcard with the index. + + XPath counts the elements starting at one, so we add one to :py:attr:`index`. + """ + return f'*[{self.index + 1}]' + + +class KeySegment: + """ + Represent a member of an open JSON-able object on a path to + the erroneous element. + + Unlike an :py:class:`ElementSegment`, which names an element prescribed by + the meta-model, a key is known only at run time, and can be any string + at all. + """ + #: Element of the ```` which carries the key + element: Final[Element] + + #: Key of the member + key: Final[str] + + def __init__( + self, + element: Element, + key: str + ) -> None: + """Initialize with the given values.""" + self.element = element + self.key = key + + def __str__(self) -> str: + """ + Render the segment as a predicate on the ```` child element. + + A JSON-able object is written as an XML-RPC ````, which carries + the key of a member in a ```` child element instead of in + an attribute, so the XPath has to match on that child element. + """ + escaped = ( + self.key + .replace('&', '&') + .replace('/', '/') + .replace('<', '<') + .replace('>', '>') + .replace('"', '"') + .replace("'", ''') + ) + return f'member[name="{escaped}"]' + + +Segment = Union[ElementSegment, IndexSegment, KeySegment] + + +class Path: + """Represent the relative path to the erroneous element.""" + + def __init__(self) -> None: + """Initialize as an empty path.""" + self._segments = [] # type: List[Segment] + + @property + def segments(self) -> Sequence[Segment]: + """Get the segments of the path.""" + return self._segments + + def _prepend(self, segment: Segment) -> None: + """Insert the :paramref:`segment` in front of other segments.""" + self._segments.insert(0, segment) + + def __str__(self) -> str: + """Render the path as a relative XPath. + + We omit the leading ``/`` so that you can easily prefix it as you need. + """ + return "/".join(str(segment) for segment in self._segments) + + +class DeserializationException(Exception): + """Signal that the XML de-serialization could not be performed.""" + + #: Human-readable explanation of the exception's cause + cause: Final[str] + + #: Relative path to the erroneous value + path: Final[Path] + + def __init__( + self, + cause: str + ) -> None: + """Initialize with the given :paramref:`cause` and an empty path.""" + self.cause = cause + self.path = Path() + + +class SerializationException(Exception): + """Signal that the XML serialization could not be performed.""" + + #: Human-readable explanation of the exception's cause + cause: Final[str] + + def __init__( + self, + cause: str + ) -> None: + """Initialize with the given :paramref:`cause` and an empty path.""" + self.cause = cause + self._segments = [] # type: List[str] + + @property + def path(self) -> str: + """ + Render the path to the erroneous value as a Python access expression. + + The path points into the instance which you handed over for + the serialization, and *not* into an XML document -- at the point of + the failure, there is no document yet. For example, ``.submodels[0].id`` + tells you that the serialization broke on ``that.submodels[0].id``. + + Mind that the elements which the XML representation adds on top of + the instance contribute no segment, as they correspond to no attribute + access. This concerns the element enclosing the instance itself, and + the element which designates the model type of the value of a property. + """ + return ''.join(self._segments) + + def _prepend_property(self, name: str) -> None: + """Insert the access to the property :paramref:`name` before the path.""" + self._segments.insert(0, f'.{name}') + + def _prepend_index(self, index: int) -> None: + """Insert the access to the item at :paramref:`index` before the path.""" + self._segments.insert(0, f'[{index}]') + + def _prepend_key(self, key: str) -> None: + """ + Insert the access to the member :paramref:`key` before the path. + + Unlike a property of one of our classes, a member of an open JSON-able + object is known only at run time and can be any string at all, so it is + always rendered as a subscript. + """ + self._segments.insert(0, f'[{json.dumps(key, ensure_ascii=False)}]') + + def __str__(self) -> str: + if len(self._segments) == 0: + return self.cause + + return f'{self.path}: {self.cause}' + + +# region De-serialization + + +def parse_element_tag( + element: Element +) -> str: + """ + Extract the tag name without the namespace prefix from :paramref:`element`. + + :param element: whose tag without namespace we want to extract + :return: tag name without the namespace prefix + :raise: :py:class:`DeserializationException` if unexpected :paramref:`element` + """ + # NOTE (mristin): + # :py:mod:`xml.etree.ElementTree` spells the tag of a namespaced element + # as ``{namespace}tag``. We match that prefix in place, with + # :py:meth:`str.startswith` and an offset, instead of composing + # the ``{...}`` form: this runs for every element of every document, and + # composing it would allocate a string on each call. + if ( + element.tag.startswith('{') + and element.tag.startswith(NAMESPACE, 1) + and element.tag.startswith('}', len(NAMESPACE) + 1) + ): + return element.tag[len(NAMESPACE) + 2:] + + got_namespace, separator, tag_wo_ns = ( + element.tag.rpartition('}') + ) + + if separator: + if got_namespace.startswith('{'): + got_namespace = got_namespace[1:] + + raise DeserializationException( + f"Expected the element in the namespace {NAMESPACE!r}, " + f"but got the element {tag_wo_ns!r} in " + f"the namespace {got_namespace!r}" + ) + + raise DeserializationException( + f"Expected the element in the namespace {NAMESPACE!r}, " + f"but got the element {tag_wo_ns!r} without the namespace prefix" + ) + + +def raise_if_non_whitespace_text( + element: Element, + expected_what: str +) -> None: + """ + Check that :paramref:`element` encloses no text of its own. + + An element which holds other elements may still be spelled over several + lines, so the whitespace between its children is text as far as + :py:mod:`xml.etree.ElementTree` is concerned. Anything else is not. + + :param element: to be verified + :param expected_what: what the element was expected to enclose + :raise: + :py:class:`.DeserializationException` if there is text; + conforming to the convention about handling error paths, + the exception path is left empty. + """ + if element.text is not None and len(element.text.strip()) != 0: + raise DeserializationException( + f"Expected only {expected_what} and whitespace text, " + f"but got text: {element.text!r}" + ) + + +def read_next_start_element( + iterator: Iterator[Tuple[str, Element]], + expected_what: str +) -> Element: + """ + Read the next start element from :paramref:`iterator`. + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param expected_what: what we expected to read, for the error messages + :raise: + :py:class:`.DeserializationException` if the input ended, or if what came + next was no start element; conforming to the convention about handling + error paths, the exception path is left empty. + :return: the start element + """ + next_event_element = next(iterator, None) + if next_event_element is None: + raise DeserializationException( + f"Expected the start element for {expected_what}, " + f"but got the end-of-input" + ) + + next_event, next_element = next_event_element + if next_event != 'start': + raise DeserializationException( + f"Expected the start element for {expected_what}, " + f"but got event {next_event!r} and element {next_element.tag!r}" + ) + + return next_element + + +def raise_if_has_tail_or_attrib( + element: Element +) -> None: + """ + Check that :paramref:`element` has no trailing text and no attributes. + + :param element: to be verified + :raise: + :py:class:`.DeserializationException` if trailing text or attributes; + conforming to the convention about handling error paths, + the exception path is left empty. + """ + if element.tail is not None and len(element.tail.strip()) != 0: + raise DeserializationException( + f"Expected no trailing text, but got: {element.tail!r}" + ) + + if element.attrib is not None and len(element.attrib) > 0: + raise DeserializationException( + f"Expected no attributes, but got: {element.attrib}" + ) + + +def read_end_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> Element: + """ + Read the end element corresponding to the start :paramref:`element` + from :paramref:`iterator`. + + :param element: corresponding start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + """ + next_event_element = next(iterator, None) + if next_event_element is None: + raise DeserializationException( + f"Expected the end element for {element.tag}, " + f"but got the end-of-input" + ) + + next_event, next_element = next_event_element + if next_event != "end" or next_element.tag != element.tag: + raise DeserializationException( + f"Expected the end element for {element.tag!r}, " + f"but got the event {next_event!r} and element {next_element.tag!r}" + ) + + raise_if_has_tail_or_attrib(next_element) + + return next_element + + +def read_text_from_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> str: + """ + Extract the text from the :paramref:`element`, and read + the end element from :paramref:`iterator`. + + The :paramref:`element` is expected to contain text. Otherwise, + it is considered as unexpected input. + + :param element: start element enclosing the text + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + """ + raise_if_has_tail_or_attrib(element) + + text = element.text + + end_element = read_end_element( + element, + iterator, + ) + + if text is None: + if end_element.text is None: + raise DeserializationException( + "Expected an element with text, but got an element with no text." + ) + + text = end_element.text + + return text + + +def read_str_from_element_text( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> str: + """ + Parse the text of :paramref:`element` as a string, and + read the corresponding end element from :paramref:`iterator`. + + If there is no text, empty string is returned. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed value + """ + # NOTE (mristin): + # We do not use ``read_text_from_element`` as that function expects + # the ``element`` to contain *some* text. In contrast, this function + # can also deal with empty text, in which case it returns an empty string. + + text = element.text + + end_element = read_end_element( + element, + iterator + ) + + if text is None: + text = end_element.text + + raise_if_has_tail_or_attrib(element) + result = ( + text + if text is not None + else "" + ) + + return result + + +#: Match a run of the whitespace characters which XSD knows +XS_WHITESPACE_RE = re.compile(r"[ \t\n\r]+") + + +def collapse_whitespace(text: str) -> str: + """ + Normalize :paramref:`text` the way ``whiteSpace="collapse"`` prescribes. + + Every atomic XSD type except a string, and every type derived from one + by restriction, fixes ``whiteSpace`` to ``collapse``, and a schema author + can not change it. A tab, a line feed and a carriage return each become + a space, a run of spaces becomes one space, and the leading and trailing + spaces go. Only then is the result a lexical representation to be matched. + + Mind that this strips only the whitespace *around* the value: a space + within it survives as a single space, so ``2 3`` becomes ``2 3``, which + is still no number. + + See: https://www.w3.org/TR/xmlschema-2/#rf-whiteSpace + + :param text: to be normalized + :return: normalized text + """ + return XS_WHITESPACE_RE.sub(" ", text).strip(" ") + + +# endregion + + +# region Serialization + + +class Writer: + """ + Write the XML elements of a single document to :py:attr:`~stream`. + + The namespace is specified on the very first element alone, and on none of + the elements which follow it, so one writer serves exactly one document. + """ + + #: Stream to be written to + stream: Final[TextIO] + + #: Method pointer to be invoked for writing the start element with or without + #: specifying a namespace (depending on the state of the writer) + write_start_element: Callable[ + [str], + None + ] + + #: Method pointer to be invoked for writing an empty element with or without + #: specifying a namespace (depending on the state of the writer) + write_empty_element: Callable[ + [str], + None + ] + + # NOTE (mristin): + # The serialization procedure is quite rigid. We leverage the specifics of + # the serialization procedure to optimize the code a bit. + # + # Namely, we model the writing of the XML elements as a state machine. + # The namespace is only specified for the very first element. All the subsequent + # elements will *not* have the namespace specified. We implement that behavior by + # using pointers to methods, as Python treats the methods as first-class citizens. + # + # The ``write_start_element`` will point to + # ``_write_first_start_element_with_namespace`` on the *first* invocation. + # Afterwards, it will be redirected to ``_write_start_element_without_namespace``. + # + # Analogously for ``write_empty_element``. + # + # Please see the implementation for the details, but this should give you at least + # a rough overview. + + def _write_first_start_element_with_namespace( + self, + name: str + ) -> None: + """ + Write the start element with the tag name :paramref:`name` and specify + its namespace. + + The :py:attr:`~write_start_element` is set to + :py:meth:`~_write_start_element_without_namespace` after the first invocation + of this method. + + :param name: of the element tag. Expected to contain no XML special characters. + """ + self.stream.write(f'<{name} xmlns="{NAMESPACE}">') + + # NOTE (mristin): + # Any subsequence call to `write_start_element` or `write_empty_element` + # should not specify the namespace of the element as we specified now already + # specified it. + self.write_start_element = self._write_start_element_without_namespace + self.write_empty_element = self._write_empty_element_without_namespace + + def _write_start_element_without_namespace( + self, + name: str + ) -> None: + """ + Write the start element with the tag name :paramref:`name`. + + The first element, written *before* this one, is expected to have been + already written with the namespace specified. + + :param name: of the element tag. Expected to contain no XML special characters. + """ + self.stream.write(f'<{name}>') + + def write_end_element( + self, + name: str + ) -> None: + """ + Write the end element with the tag name :paramref:`name`. + + :param name: of the element tag. Expected to contain no XML special characters. + """ + self.stream.write(f'') + + def _write_first_empty_element_with_namespace( + self, + name: str + ) -> None: + """ + Write the first (and only) empty element with the tag name :paramref:`name`. + + No elements are expected to be written to the stream afterwards. The element + includes the namespace specification. + + :param name: of the element tag. Expected to contain no XML special characters. + """ + self.stream.write(f'<{name} xmlns="{NAMESPACE}"/>') + self.write_empty_element = self._raise_if_write_element_called_again + self.write_start_element = self._raise_if_write_element_called_again + + def _raise_if_write_element_called_again( + self, + name: str + ) -> None: + raise AssertionError( + f"We expected to call ``_write_first_empty_element_with_namespace`` " + f"only once. This is an unexpected second call for writing " + f"an (empty or non-empty) element with the tag name: {name!r}" + ) + + def _write_empty_element_without_namespace( + self, + name: str + ) -> None: + """ + Write the empty element with the tag name :paramref:`name`. + + The call to this method is expected to occur *after* the enclosing element with + a specified namespace has been written. + + :param name: of the element tag. Expected to contain no XML special characters. + """ + self.stream.write(f'<{name}/>') + + def __init__( + self, + stream: TextIO + ) -> None: + """ + Initialize the writer to write to :paramref:`stream`. + + The first element will include the :py:attr:`NAMESPACE`. Every other + element will not have the namespace specified. + + :param stream: where to write to + """ + self.stream = stream + self.write_start_element = ( + self._write_first_start_element_with_namespace + ) + self.write_empty_element = ( + self._write_first_empty_element_with_namespace + ) + + +# endregion diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlization.py b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlization.py new file mode 100644 index 000000000..4bdbb0da7 --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/dummy/xmlization.py @@ -0,0 +1,2476 @@ +""" +Read and write model instances as XML. + +For reading, we provide different reading functions, each handling a different kind +of input. All the reading functions operate in one pass, *i.e.*, the source is read +incrementally and the complete XML is not held in memory. + +We provide the following four reading functions (where ``X`` represents the name of +the class): + +1) ``X_from_iterparse`` reads from a stream of ``(event, element)`` tuples coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]``. If you do not trust the source, please consider + using `defusedxml.ElementTree`_. +2) ``X_from_stream`` reads from the given text stream. +3) ``X_from_file`` reads from a file on disk. +4) ``X_from_str`` reads from the given string. + +The functions ``X_from_stream``, ``X_from_file`` and ``X_from_str`` provide +an extra parameter, ``has_iterparse``, which allows you to use a parsing library +different from :py:mod:`xml.etree.ElementTree`. For example, you can pass in +`defusedxml.ElementTree`_. + +.. _defusedxml.ElementTree: https://pypi.org/project/defusedxml/#defusedxml-elementtree + +All XML elements are expected to live in the :py:attr:`~NAMESPACE`. + +For writing, use the function :py:func:`dummy.xmlization.write` which +translates the instance of the model into an XML document and writes it in one pass +to the stream. + +The writing raises a :py:class:`SerializationException` if it can not serialize +the instance, be it because the stream failed or because a value could not be +written. The path of the exception points to the culprit as a Python access +expression, *e.g.*, ``.submodels[0].id``, so that you can find it in the instance +which you handed over. + +Here is an example usage how to de-serialize from a file: + +.. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.read_some_item_from_file( + path + ) + + # Do something with the ``instance`` + +Here is another code example where we serialize the instance: + +.. code-block:: + + import pathlib + + import dummy.types as our_types + import dummy.xmlization as our_xmlization + + instance = SomeItem( + ... # some constructor arguments + ) + + pth = pathlib.Path(...) + with pth.open("wt") as fid: + our_xmlization.write(instance, fid) +""" + + +# This code has been automatically generated by aas-core-codegen. +# Do NOT edit or append. + + +import base64 +import enum +import io +import math +import os +import re +import sys +from typing import ( + Any, + Callable, + Dict, + Iterator, + List, + Mapping, + NoReturn, + Optional, + Sequence, + TextIO, + Tuple, + TypeVar, + TYPE_CHECKING +) +import xml.etree.ElementTree + +if sys.version_info >= (3, 8): + from typing import Final +else: + from typing_extensions import Final + +import dummy.stringification as our_stringification +import dummy.types as our_types +import dummy.xmlcommon as our_xmlcommon +from dummy.xmlcommon import ( + XS_WHITESPACE_RE, + collapse_whitespace, + parse_element_tag, + raise_if_has_tail_or_attrib, + read_end_element, + read_next_start_element, + read_str_from_element_text, + read_text_from_element +) + +# See: https://stackoverflow.com/questions/55076778/why-isnt-this-function-type-annotated-correctly-error-missing-type-parameters +if TYPE_CHECKING: + PathLike = os.PathLike[Any] +else: + PathLike = os.PathLike + + +#: XML namespace in which all the elements are expected to reside +NAMESPACE = our_xmlcommon.NAMESPACE + + +# region De-serialization + + +Element = our_xmlcommon.Element +HasIterparse = our_xmlcommon.HasIterparse + +ElementSegment = our_xmlcommon.ElementSegment +IndexSegment = our_xmlcommon.IndexSegment +KeySegment = our_xmlcommon.KeySegment +Segment = our_xmlcommon.Segment +Path = our_xmlcommon.Path + +DeserializationException = our_xmlcommon.DeserializationException +SerializationException = our_xmlcommon.SerializationException + + +def _with_elements_cleared_after_yield( + iterator: Iterator[Tuple[str, Element]] +) -> Iterator[Tuple[str, Element]]: + """ + Map the :paramref:`iterator` such that the element is ``clear()``'ed + *after* every ``yield``. + + :param iterator: to be mapped + :yield: event and element from :paramref:`iterator` + """ + for event, element in iterator: + yield event, element + element.clear() + + +def item_from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Item: + """ + Read an instance of :py:class:`.types.Item` from + the :paramref:`iterator`. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.item_from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Item` read from + :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_item_as_element, + 'Item' + ) + + +def item_from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Item: + """ + Read an instance of :py:class:`.types.Item` from + the :paramref:`stream`. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.item_from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance of + :py:class:`.types.Item` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Item` read from + :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def item_from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Item: + """ + Read an instance of :py:class:`.types.Item` from + the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.item_from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance of + :py:class:`.types.Item` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Item` read from + :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def item_from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Item: + """ + Read an instance of :py:class:`.types.Item` from + the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.item_from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance of + :py:class:`.types.Item` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Item` read from + :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def some_item_from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` from + the :paramref:`iterator`. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.some_item_from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SomeItem` read from + :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_some_item_as_element, + 'SomeItem' + ) + + +def some_item_from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` from + the :paramref:`stream`. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.some_item_from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance of + :py:class:`.types.SomeItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SomeItem` read from + :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return some_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def some_item_from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` from + the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.some_item_from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance of + :py:class:`.types.SomeItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SomeItem` read from + :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return some_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def some_item_from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` from + the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.some_item_from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance of + :py:class:`.types.SomeItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SomeItem` read from + :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return some_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def special_item_from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` from + the :paramref:`iterator`. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.special_item_from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SpecialItem` read from + :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_special_item_as_element, + 'SpecialItem' + ) + + +def special_item_from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` from + the :paramref:`stream`. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.special_item_from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance of + :py:class:`.types.SpecialItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SpecialItem` read from + :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return special_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def special_item_from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` from + the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.special_item_from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance of + :py:class:`.types.SpecialItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SpecialItem` read from + :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return special_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def special_item_from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` from + the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.special_item_from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance of + :py:class:`.types.SpecialItem` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.SpecialItem` read from + :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return special_item_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def container_from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` from + the :paramref:`iterator`. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.container_from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Container` read from + :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_container_as_element, + 'Container' + ) + + +def container_from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` from + the :paramref:`stream`. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.container_from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance of + :py:class:`.types.Container` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Container` read from + :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return container_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def container_from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` from + the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.container_from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance of + :py:class:`.types.Container` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Container` read from + :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return container_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def container_from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` from + the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.container_from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance of + :py:class:`.types.Container` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Container` read from + :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return container_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def wrapper_from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` from + the :paramref:`iterator`. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.wrapper_from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Wrapper` read from + :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_wrapper_as_element, + 'Wrapper' + ) + + +def wrapper_from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` from + the :paramref:`stream`. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.wrapper_from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance of + :py:class:`.types.Wrapper` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Wrapper` read from + :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return wrapper_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def wrapper_from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` from + the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.wrapper_from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance of + :py:class:`.types.Wrapper` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Wrapper` read from + :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return wrapper_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def wrapper_from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` from + the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.wrapper_from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance of + :py:class:`.types.Wrapper` in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Wrapper` read from + :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return wrapper_from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def from_iterparse( + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Class: + """ + Read an instance from the :paramref:`iterator`. + + The type of the instance is determined by the very first start element. + + Example usage: + + .. code-block:: + + import pathlib + import xml.etree.ElementTree as ET + + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + with path.open("rt") as fid: + iterator = ET.iterparse( + source=fid, + events=['start', 'end'] + ) + instance = our_xmlization.from_iterparse( + iterator + ) + + # Do something with the ``instance`` + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance of :py:class:`.types.Class` read from the :paramref:`iterator` + """ + return _read_instance_from_iterparse( + iterator, + _read_as_element, + 'an instance' + ) + + +def from_stream( + stream: TextIO, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Class: + """ + Read an instance from the :paramref:`stream`. + + The type of the instance is determined by the very first start element. + + Example usage: + + .. code-block:: + + import dummy.xmlization as our_xmlization + + with open_some_stream_over_network(...) as stream: + instance = our_xmlization.from_stream( + stream + ) + + # Do something with the ``instance`` + + :param stream: + representing an instance in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance read from :paramref:`stream` + """ + iterator = has_iterparse.iterparse( + stream, + ['start', 'end'] + ) + return from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def from_file( + path: PathLike, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Class: + """ + Read an instance from the file at the :paramref:`path`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + path = pathlib.Path(...) + instance = our_xmlization.from_file( + path + ) + + # Do something with the ``instance`` + + :param path: + to the file representing an instance in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance read from the file at :paramref:`path` + """ + with open(os.fspath(path), "rt", encoding='utf-8') as fid: + iterator = has_iterparse.iterparse( + fid, + ['start', 'end'] + ) + return from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +def from_str( + text: str, + has_iterparse: HasIterparse = xml.etree.ElementTree +) -> our_types.Class: + """ + Read an instance from the :paramref:`text`. + + Example usage: + + .. code-block:: + + import pathlib + import dummy.xmlization as our_xmlization + + text = "<...>..." + instance = our_xmlization.from_str( + text + ) + + # Do something with the ``instance`` + + :param text: + representing an instance in XML + :param has_iterparse: + Module containing ``iterparse`` function. + + Default is to use :py:mod:`xml.etree.ElementTree` from the standard + library. If you have to deal with malicious input, consider using + a library such as `defusedxml.ElementTree`_. + :raise: :py:class:`DeserializationException` if unexpected input + :return: + Instance read from :paramref:`text` + """ + iterator = has_iterparse.iterparse( + io.StringIO(text), + ['start', 'end'] + ) + return from_iterparse( + _with_elements_cleared_after_yield(iterator) + ) + + +# NOTE (mristin): +# Directly using the iterator turned out to result in very complex function +# designs. The design became much simpler as soon as we considered one look-ahead +# element. We came up finally with the following pattern which all the protected +# reading functions below roughly follow: +# +# ..code-block:: +# +# _read_*( +# look-ahead element, +# iterator +# ) -> result +# +# The reading functions all read from the ``iterator`` coming from +# :py:func:`xml.etree.ElementTree.iterparse` with the argument +# ``events=["start", "end"]``. The exception :py:class:`.DeserializationException` +# is raised in case of unexpected input. +# +# The reading functions are responsible to read the end element corresponding to the +# start look-ahead element. +# +# When it comes to error reporting, we use exceptions. The exceptions are raised in +# the *callee*, as usual. However, the context of the exception, such as the error path, +# is added in the *caller*, as only the caller knows the context of +# the lookahead-element. In particular, prepending the path segment corresponding to +# the lookahead-element is the responsibility of the *caller*, and not of +# the *callee*. + + +_ValueT = TypeVar("_ValueT") + +#: Read the content of an element which has already been opened, and read +#: the corresponding end element as well +_ContentReader = Callable[ + [Element, Iterator[Tuple[str, Element]]], + _ValueT +] + + +def _read_named_element( + element: Element, + iterator: Iterator[Tuple[str, Element]], + expected_tag: str, + read_content: _ContentReader[_ValueT] +) -> _ValueT: + """ + Verify that :paramref:`element` bears the :paramref:`expected_tag`, and + delegate the reading of its content to :paramref:`read_content`. + + This is the only place where an element's tag is checked against the tag which + its container prescribes -- the XML name of a class, ```` for a list item, or + ````, ````, *etc.* for a tuple item. + + :param element: look-ahead element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param expected_tag: expected tag of :paramref:`element` + :param read_content: to read the content of :paramref:`element` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed value + """ + tag_wo_ns = parse_element_tag(element) + if tag_wo_ns != expected_tag: + raise DeserializationException( + f"Expected an element with the tag {expected_tag!r}, " + f"but got an element with tag: {tag_wo_ns!r}" + ) + + return read_content(element, iterator) + + +def _read_dispatched( + element: Element, + iterator: Iterator[Tuple[str, Element]], + dispatch: Mapping[str, _ContentReader[_ValueT]], + expected_what: str +) -> _ValueT: + """ + Read the instance of :paramref:`element` by dispatching on its own tag. + + An instance element is self-describing: its tag *is* its model type. + + The end element corresponding to :paramref:`element` will be read as well. + + :param element: start element of the instance + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param dispatch: to read the instance as a sequence, by its model type + :param expected_what: what we expected to read, for the error messages + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + tag_wo_ns = parse_element_tag(element) + + read_as_sequence = dispatch.get(tag_wo_ns, None) + if read_as_sequence is None: + raise DeserializationException( + f"Expected the element tag to be a valid model type " + f"of {expected_what}, " + f"but got tag {tag_wo_ns!r}" + ) + + return read_as_sequence(element, iterator) + + +def _read_properties( + element: Element, + iterator: Iterator[Tuple[str, Element]], + readers: Mapping[str, _ContentReader[Any]] +) -> Mapping[str, Any]: + """ + Read the properties of an instance as the children of :paramref:`element`. + + The end element corresponding to :paramref:`element` will be read as well. + + A property may come at most once, and an unknown tag is refused outright. + + The property is marked on the error path here, once for all the properties, + instead of in every reader: the tag of the child element *is* the XML name of + the property which we are reading. + + :param element: start element, parent of the properties + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param readers: to read the content of a property, by its XML name + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed values, by the XML name of the property + """ + if element.text is not None and len(element.text.strip()) != 0: + raise DeserializationException( + f"Expected only XML elements representing the properties " + f"and whitespace text, but got text: {element.text!r}" + ) + + raise_if_has_tail_or_attrib(element) + + values = dict() # type: Dict[str, Any] + + while True: + # NOTE (mristin): + # We pull the next property element here instead of delegating it to + # a helper. A call is not free in Python, and this loop runs once for + # every property of every instance. + next_event_element = next(iterator, None) + if next_event_element is None: + raise DeserializationException( + f"Expected a property element or the end element corresponding " + f"to {element.tag}, but got the end-of-input" + ) + + next_event, prop_element = next_event_element + if next_event == 'end' and prop_element.tag == element.tag: + # We reached the end element enclosing the properties. + break + + if next_event != 'start': + raise DeserializationException( + f"Expected a start element corresponding to a property, " + f"but got event {next_event!r} " + f"and element {prop_element.tag!r}" + ) + + try: + tag_wo_ns = parse_element_tag(prop_element) + + # NOTE (mristin): + # A tag already in ``values`` can only have got there by being read, + # so its presence means that the property comes a second time. + if tag_wo_ns in values: + raise DeserializationException( + f"Property {tag_wo_ns!r} occurred more than once" + ) + + reader = readers.get(tag_wo_ns, None) + if reader is None: + raise DeserializationException( + f"Expected an element representing a property, " + f"but got an element with unexpected tag: {tag_wo_ns!r}" + ) + + values[tag_wo_ns] = reader(prop_element, iterator) + except DeserializationException as exception: + exception.path._prepend(ElementSegment(prop_element)) + raise + + return values + + +def _read_list_of_items( + element: Element, + iterator: Iterator[Tuple[str, Element]], + read_item: _ContentReader[_ValueT] +) -> List[_ValueT]: + """ + Read the children of :paramref:`element` as a list of items. + + :paramref:`read_item` is responsible for verifying the tag of each item + element itself -- *e.g.*, by wrapping a scalar/enumeration reader with + :py:func:`_read_named_element`, or by relying on a class's own dispatch by + its natural element tag. + + The end element corresponding to :paramref:`element` will be read as well. + + :param element: start element enclosing the list + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param read_item: to read a single item, including its own end element + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed items + """ + if element.text is not None and len(element.text.strip()) != 0: + raise DeserializationException( + f"Expected only item elements and whitespace text, " + f"but got text: {element.text!r}" + ) + + result = [] # type: List[_ValueT] + + while True: + # NOTE (mristin): + # We pull the next item element here instead of delegating it to a helper, + # as this loop runs once for every item of every list. + next_event_element = next(iterator, None) + if next_event_element is None: + raise DeserializationException( + f"Expected an item element or the end element corresponding " + f"to {element.tag}, but got the end-of-input" + ) + + next_event, item_element = next_event_element + if next_event == 'end' and item_element.tag == element.tag: + # We reached the end element enclosing the items. + break + + if next_event != 'start': + raise DeserializationException( + f"Expected a start element corresponding to an item, " + f"but got event {next_event!r} " + f"and element {item_element.tag!r}" + ) + + try: + item = read_item(item_element, iterator) + except DeserializationException as exception: + exception.path._prepend(IndexSegment(item_element, len(result))) + raise + + result.append(item) + + return result + + +def _read_instance_from_iterparse( + iterator: Iterator[Tuple[str, Element]], + read_as_element: _ContentReader[_ValueT], + expected_what: str +) -> _ValueT: + """ + Read an instance from :paramref:`iterator`, starting at its start element. + + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :param read_as_element: to read the instance, including its end element + :param expected_what: what we expected to read, for the error messages + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + next_element = read_next_start_element(iterator, expected_what) + + try: + return read_as_element(next_element, iterator) + except DeserializationException as exception: + exception.path._prepend(ElementSegment(next_element)) + raise exception + + +#: Match the lexical space of ``xs:long``. +#: +#: Mind the explicit ``[0-9]``: ``\d`` would match a digit of any script. +#: +#: See: https://www.w3.org/TR/xmlschema-2/#long +_XS_LONG_RE = re.compile(r"(\+|-)?[0-9]+") + + +def _read_int_from_element_text( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> int: + """ + Parse the text of :paramref:`element` as an integer, and + read the corresponding end element from :paramref:`iterator`. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: + :py:class:`DeserializationException` if unexpected input, including + a value outside of the 64-bit range of ``xs:long`` + :return: parsed value + """ + text = collapse_whitespace( + read_text_from_element( + element, + iterator + ) + ) + + # NOTE (mristin): + # ``int`` is far too permissive to be handed the text directly: it takes + # a digit group separator as in ``1_0``, surrounding whitespace, and + # a digit of any script -- the Arabic-Indic ``۵`` would be read as 5. + # Mind that it is checked with ``fullmatch`` and not with ``match``: ``$`` + # would also match just before a trailing newline. + # + # See: https://www.w3.org/TR/xmlschema-2/#long + if _XS_LONG_RE.fullmatch(text) is None: + raise DeserializationException( + f"Expected a value as xs:long, " + f"but got an element with text: {text!r}" + ) + + # NOTE (mristin): + # We count the significant digits before we call ``int``. The conversion is + # quadratic in the length of the text, and Python refuses a text of more than + # 4300 digits, leading zeros included, although ``xs:long`` allows arbitrarily + # many leading zeros. The largest magnitude, 2^63, has 19 digits. + digits = text.lstrip("+-").lstrip("0") + if len(digits) > 19: + raise DeserializationException( + f"Expected a value as xs:long, " + f"but got an element with text out of its range: {text!r}" + ) + + value = int(digits) if len(digits) > 0 else 0 + if text[0] == "-": + value = -value + + # NOTE (mristin): + # An ``int`` is unbounded in Python, while ``xs:long`` is a 64-bit integer, + # so the range has to be checked explicitly, as a number of 19 digits might + # still not fit. Every other target gets this for free from a parser which + # refuses what does not fit. + if value < -9223372036854775808 or value > 9223372036854775807: + raise DeserializationException( + f"Expected a value as xs:long, " + f"but got an element with text out of its range: {text!r}" + ) + + return value + + +def _read_list_of__item( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> List[our_types.Item]: + """ + Read the items of :paramref:`element` as a list of + :py:class:`.types.Item`. + """ + return _read_list_of_items( + element, + iterator, + _read_item_as_element + ) + + +def _read_list_of__some_item( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> List[our_types.SomeItem]: + """ + Read the items of :paramref:`element` as a list of + :py:class:`.types.SomeItem`. + """ + return _read_list_of_items( + element, + iterator, + _read_some_item_as_element + ) + + +def _read_list_of__special_item( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> List[our_types.SpecialItem]: + """ + Read the items of :paramref:`element` as a list of + :py:class:`.types.SpecialItem`. + """ + return _read_list_of_items( + element, + iterator, + _read_special_item_as_element + ) + + +def _read_item_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Item: + """ + Read an instance of :py:class:`.types.Item` from + :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_dispatched( + element, + iterator, + _DISPATCH_FOR_ITEM, + "a concrete instance of 'Item'" + ) + + +def _read_some_item_as_sequence( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` + as a sequence of XML-encoded properties. + + The end element corresponding to the :paramref:`element` will be + read as well. + + :param element: start element, parent of the sequence + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + values = _read_properties( + element, + iterator, + _READERS_FOR_SOME_ITEM + ) + + the_name: Optional[str] = values.get('name') + the_nickname: Optional[str] = values.get('nickname') + + if the_name is None: + raise DeserializationException( + "The required property 'name' is missing" + ) + + return our_types.SomeItem( + the_name, + the_nickname + ) + + +def _read_some_item_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SomeItem: + """ + Read an instance of :py:class:`.types.SomeItem` from + :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_dispatched( + element, + iterator, + _DISPATCH_FOR_SOME_ITEM, + "a concrete instance of 'SomeItem'" + ) + + +def _read_special_item_as_sequence( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` + as a sequence of XML-encoded properties. + + The end element corresponding to the :paramref:`element` will be + read as well. + + :param element: start element, parent of the sequence + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + values = _read_properties( + element, + iterator, + _READERS_FOR_SPECIAL_ITEM + ) + + the_name: Optional[str] = values.get('name') + the_nickname: Optional[str] = values.get('nickname') + the_level: Optional[int] = values.get('level') + + if the_name is None: + raise DeserializationException( + "The required property 'name' is missing" + ) + + if the_level is None: + raise DeserializationException( + "The required property 'level' is missing" + ) + + return our_types.SpecialItem( + the_name, + the_level, + the_nickname + ) + + +def _read_special_item_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.SpecialItem: + """ + Read an instance of :py:class:`.types.SpecialItem` from + :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_named_element( + element, + iterator, + 'specialItem', + _read_special_item_as_sequence + ) + + +def _read_container_as_sequence( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` + as a sequence of XML-encoded properties. + + The end element corresponding to the :paramref:`element` will be + read as well. + + :param element: start element, parent of the sequence + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + values = _read_properties( + element, + iterator, + _READERS_FOR_CONTAINER + ) + + the_items: Optional[List[our_types.Item]] = values.get('items') + the_some_items: Optional[List[our_types.SomeItem]] = values.get('someItems') + the_special_items: Optional[List[our_types.SpecialItem]] = values.get('specialItems') + + if the_items is None: + raise DeserializationException( + "The required property 'items' is missing" + ) + + if the_some_items is None: + raise DeserializationException( + "The required property 'someItems' is missing" + ) + + return our_types.Container( + the_items, + the_some_items, + the_special_items + ) + + +def _read_container_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Container: + """ + Read an instance of :py:class:`.types.Container` from + :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_named_element( + element, + iterator, + 'container', + _read_container_as_sequence + ) + + +def _read_wrapper_as_sequence( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` + as a sequence of XML-encoded properties. + + The end element corresponding to the :paramref:`element` will be + read as well. + + :param element: start element, parent of the sequence + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + values = _read_properties( + element, + iterator, + _READERS_FOR_WRAPPER + ) + + the_container: Optional[our_types.Container] = values.get('container') + + if the_container is None: + raise DeserializationException( + "The required property 'container' is missing" + ) + + return our_types.Wrapper( + the_container + ) + + +def _read_wrapper_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Wrapper: + """ + Read an instance of :py:class:`.types.Wrapper` from + :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_named_element( + element, + iterator, + 'wrapper', + _read_wrapper_as_sequence + ) + + +def _read_as_element( + element: Element, + iterator: Iterator[Tuple[str, Element]] +) -> our_types.Class: + """ + Read an instance from :paramref:`iterator`, including the end element. + + :param element: start element + :param iterator: + Input stream of ``(event, element)`` coming from + :py:func:`xml.etree.ElementTree.iterparse` with the argument + ``events=["start", "end"]`` + :raise: :py:class:`DeserializationException` if unexpected input + :return: parsed instance + """ + return _read_dispatched( + element, + iterator, + _GENERAL_DISPATCH, + 'a concrete instance' + ) + + +#: Dispatch XML class names to read-as-sequence functions +#: corresponding to concrete descendants of Item +_DISPATCH_FOR_ITEM: Mapping[ + str, + Callable[ + [ + Element, + Iterator[Tuple[str, Element]] + ], + our_types.Item + ] +] = { + 'someItem': _read_some_item_as_sequence, + 'specialItem': _read_special_item_as_sequence, +} + + +#: Dispatch XML class names to read-as-sequence functions +#: corresponding to SomeItem and its concrete descendants +_DISPATCH_FOR_SOME_ITEM: Mapping[ + str, + Callable[ + [ + Element, + Iterator[Tuple[str, Element]] + ], + our_types.SomeItem + ] +] = { + 'someItem': _read_some_item_as_sequence, + 'specialItem': _read_special_item_as_sequence, +} + + +#: Dispatch XML class names to read-as-sequence functions +#: corresponding to the concrete classes +_GENERAL_DISPATCH: Mapping[ + str, + Callable[ + [ + Element, + Iterator[Tuple[str, Element]] + ], + our_types.Class + ] +] = { + 'someItem': _read_some_item_as_sequence, + 'specialItem': _read_special_item_as_sequence, + 'container': _read_container_as_sequence, + 'wrapper': _read_wrapper_as_sequence, +} + + +#: Read the content of a property of +#: :py:class:`.types.SomeItem`, by the XML name of the property +_READERS_FOR_SOME_ITEM: Mapping[ + str, + _ContentReader[Any] +] = { + 'name': read_str_from_element_text, + 'nickname': read_str_from_element_text, +} + + +#: Read the content of a property of +#: :py:class:`.types.SpecialItem`, by the XML name of the property +_READERS_FOR_SPECIAL_ITEM: Mapping[ + str, + _ContentReader[Any] +] = { + 'name': read_str_from_element_text, + 'nickname': read_str_from_element_text, + 'level': _read_int_from_element_text, +} + + +#: Read the content of a property of +#: :py:class:`.types.Container`, by the XML name of the property +_READERS_FOR_CONTAINER: Mapping[ + str, + _ContentReader[Any] +] = { + 'items': _read_list_of__item, + 'someItems': _read_list_of__some_item, + 'specialItems': _read_list_of__special_item, +} + + +#: Read the content of a property of +#: :py:class:`.types.Wrapper`, by the XML name of the property +_READERS_FOR_WRAPPER: Mapping[ + str, + _ContentReader[Any] +] = { + 'container': _read_container_as_sequence, +} + + +# endregion + + +# region Serialization + + +def _attribute_to_property( + exception: Exception, + prop_name: Optional[str] +) -> NoReturn: + """ + Re-raise the :paramref:`exception` as a failure of the property + :paramref:`prop_name`. + + Every writer funnels its failures through this function, so that the path to + the culprit is built up as the stack unwinds: a writer knows the property whose + value it writes, and nothing below it does. + + A :paramref:`prop_name` of ``None`` prepends nothing to the path. It does *not* + mean that no property is involved. It means that the access which leads to + the value is recorded by a writer further up the stack, so recording it here + as well would spell one step of the path twice. + + For example, the writer of an item of a list is given no property name. The list + writer records its own property and the loop over the items records the index, so + that ``.submodel_elements[3]`` is assembled from ``submodel_elements`` above and + ``3`` around the item. Were the item to contribute ``submodel_elements`` as well, + the path would read ``.submodel_elements[3].submodel_elements``. + + Likewise, an instance nested in the element of a property is written by + a dispatch on its run-time type, and a dispatch knows nothing about where + the instance came from. It is :py:func:`_write_nested_element` above it which + knows that the instance sits in ``.value``, so the writer which the dispatch + selects contributes no segment of its own. + + The path therefore stays empty only when the value which broke *is* the one you + handed over, which is the failure :py:func:`write` funnels here: there is no + access leading to it to report. + + We deliberately catch *any* exception, and not only the failures of + the underlying stream. The type annotations are not enforced at run-time, so + a property can hold a value which we can not serialize, and telling you where + that value sits is much more helpful than the bare exception. The original + exception is kept as the cause of the raised one. + + :param exception: to be re-raised + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :raise: :py:class:`SerializationException` always + """ + if isinstance(exception, SerializationException): + if prop_name is not None: + exception._prepend_property(prop_name) + + raise exception + + failure = SerializationException(str(exception)) + if prop_name is not None: + failure._prepend_property(prop_name) + + raise failure from exception + + +def _attribute_to_item( + exception: Exception, + index: int +) -> NoReturn: + """ + Re-raise the :paramref:`exception` as a failure of the item at + :paramref:`index`. + + This is the counterpart of :py:func:`_attribute_to_property` for the items of + a list and of a tuple. The item's own element contributes no segment to + the path: an item is selected by its position, and not by its element tag. + + :param exception: to be re-raised + :param index: of the item which was being written + :raise: :py:class:`SerializationException` always + """ + if isinstance(exception, SerializationException): + exception._prepend_index(index) + raise exception + + failure = SerializationException(str(exception)) + failure._prepend_index(index) + raise failure from exception + + +def _write_int_as_element( + name: str, + prop_name: Optional[str], + value: int, + serializer: '_Serializer' +) -> None: + """ + Write the :paramref:`value` of an integer enclosed in + the :paramref:`name` element. + + :param name: of the corresponding element tag + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param value: to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + serializer.writer.stream.write(str(value)) + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_str_as_element( + name: str, + prop_name: Optional[str], + value: str, + serializer: '_Serializer' +) -> None: + """ + Write the :paramref:`value` of a string enclosed in + the :paramref:`name` element. + + :param name: of the corresponding element tag + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param value: to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + + # NOTE (mristin): + # We ran ``timeit`` on manual code which escaped XML special characters with + # a dictionary, and on another snippet which called three ``.replace()``. + # The code with ``.replace()`` was an order of magnitude faster on our + # computers. + # + # The escaping is written out here, and not put in a function of its own, + # since a string is the commonest value in a meta-model and a call is not free. + serializer.writer.stream.write( + value.replace('&', '&').replace('<', '<').replace('>', '>') + ) + + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_list_of_instances( + name: str, + prop_name: Optional[str], + items: Sequence[our_types.Class], + serializer: '_Serializer' +) -> None: + """ + Write :paramref:`items` enclosed in the :paramref:`name` element. + + Every item writes the element which designates its model type, so no positional + tag is necessary. If there are no items, the enclosing element is collapsed to + an empty one. + + :param name: of the enclosing element + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param items: to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + if len(items) == 0: + serializer.writer.write_empty_element(name) + else: + serializer.writer.write_start_element(name) + + for index, item in enumerate(items): + try: + serializer.visit(item) + except Exception as exception: + _attribute_to_item(exception, index) + + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_some_item_as_element( + name: str, + prop_name: Optional[str], + that: our_types.SomeItem, + serializer: '_Serializer' +) -> None: + """ + Write :paramref:`that` enclosed in the :paramref:`name` element. + + :param name: of the element tag. Expected to contain no XML special characters. + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param that: instance to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + _write_str_as_element('name', 'name', that.name, serializer) + if that.nickname is not None: + _write_str_as_element('nickname', 'nickname', that.nickname, serializer) + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_special_item_as_element( + name: str, + prop_name: Optional[str], + that: our_types.SpecialItem, + serializer: '_Serializer' +) -> None: + """ + Write :paramref:`that` enclosed in the :paramref:`name` element. + + :param name: of the element tag. Expected to contain no XML special characters. + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param that: instance to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + _write_str_as_element('name', 'name', that.name, serializer) + if that.nickname is not None: + _write_str_as_element('nickname', 'nickname', that.nickname, serializer) + _write_int_as_element('level', 'level', that.level, serializer) + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_container_as_element( + name: str, + prop_name: Optional[str], + that: our_types.Container, + serializer: '_Serializer' +) -> None: + """ + Write :paramref:`that` enclosed in the :paramref:`name` element. + + :param name: of the element tag. Expected to contain no XML special characters. + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param that: instance to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + _write_list_of_instances('items', 'items', that.items, serializer) + _write_list_of_instances('someItems', 'some_items', that.some_items, serializer) + if that.special_items is not None: + _write_list_of_instances( + 'specialItems', 'special_items', that.special_items, serializer + ) + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +def _write_wrapper_as_element( + name: str, + prop_name: Optional[str], + that: our_types.Wrapper, + serializer: '_Serializer' +) -> None: + """ + Write :paramref:`that` enclosed in the :paramref:`name` element. + + :param name: of the element tag. Expected to contain no XML special characters. + :param prop_name: + name of the property, as spelled in Python, whose value is written, or + ``None`` if the access to the value is recorded by an enclosing writer + :param that: instance to be serialized + :param serializer: to write to + :raise: :py:class:`SerializationException` if the value could not be written + """ + try: + serializer.writer.write_start_element(name) + _write_container_as_element( + 'container', 'container', that.container, serializer + ) + serializer.writer.write_end_element(name) + except Exception as exception: + _attribute_to_property(exception, prop_name) + + +class _Serializer(our_types.AbstractVisitor): + """Encode instances as XML and write them to :py:attr:`~writer`.""" + + #: Frame the XML elements of the document which we are writing + writer: Final[our_xmlcommon.Writer] + + def __init__( + self, + stream: TextIO + ) -> None: + """ + Initialize the visitor to write to :paramref:`stream`. + + The first element will include the :py:attr:`~.NAMESPACE`. Every other + element will not have the namespace specified. + + :param stream: where to write to + """ + self.writer = our_xmlcommon.Writer(stream) + + def visit_some_item( + self, + that: our_types.SomeItem + ) -> None: + """ + Serialize :paramref:`that` to :py:attr:`~stream` as an XML element. + + The enclosing XML element designates the class of the instance, where its + children correspond to the properties of the instance. + + :param that: instance to be serialized + """ + _write_some_item_as_element('someItem', None, that, self) + + def visit_special_item( + self, + that: our_types.SpecialItem + ) -> None: + """ + Serialize :paramref:`that` to :py:attr:`~stream` as an XML element. + + The enclosing XML element designates the class of the instance, where its + children correspond to the properties of the instance. + + :param that: instance to be serialized + """ + _write_special_item_as_element('specialItem', None, that, self) + + def visit_container( + self, + that: our_types.Container + ) -> None: + """ + Serialize :paramref:`that` to :py:attr:`~stream` as an XML element. + + The enclosing XML element designates the class of the instance, where its + children correspond to the properties of the instance. + + :param that: instance to be serialized + """ + _write_container_as_element('container', None, that, self) + + def visit_wrapper( + self, + that: our_types.Wrapper + ) -> None: + """ + Serialize :paramref:`that` to :py:attr:`~stream` as an XML element. + + The enclosing XML element designates the class of the instance, where its + children correspond to the properties of the instance. + + :param that: instance to be serialized + """ + _write_wrapper_as_element('wrapper', None, that, self) + + +def write(instance: our_types.Class, stream: TextIO) -> None: + """ + Write the XML representation of :paramref:`instance` to :paramref:`stream`. + + Example usage: + + .. code-block:: + + import pathlib + + import dummy.types as our_types + import dummy.xmlization as our_xmlization + + instance = SomeItem( + ... # some constructor arguments + ) + + pth = pathlib.Path(...) + with pth.open("wt") as fid: + our_xmlization.write(instance, fid) + + :param instance: to be serialized + :param stream: to write to + :raise: + :py:class:`SerializationException` if :paramref:`instance` could not be + serialized + """ + serializer = _Serializer(stream) + + try: + serializer.visit(instance) + except Exception as exception: + _attribute_to_property(exception, None) + + +def to_str(that: our_types.Class) -> str: + """ + Serialize :paramref:`that` to an XML-encoded text. + + :param that: instance to be serialized + :raise: + :py:class:`SerializationException` if :paramref:`that` could not be + serialized + :return: :paramref:`that` serialized to XML serialized to text + """ + writer = io.StringIO() + write(that, writer) + return writer.getvalue() + + +# endregion diff --git a/dev/test_data/main/python/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/python/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/python/expected/variance_of_containers/input/snippets/qualified_module_name.txt b/dev/test_data/main/python/expected/variance_of_containers/input/snippets/qualified_module_name.txt new file mode 100644 index 000000000..2995a4d0e --- /dev/null +++ b/dev/test_data/main/python/expected/variance_of_containers/input/snippets/qualified_module_name.txt @@ -0,0 +1 @@ +dummy \ No newline at end of file diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/common.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/common.ts new file mode 100644 index 000000000..4a12827cf --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/common.ts @@ -0,0 +1,604 @@ +/** + * Provide common functions shared among the modules. + */ + + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + + +/** + * Create an iterator over the given range of numbers. + * + * @param start - inclusive start of the range + * @param end - exclusive end of the range + * @returns iterator over the range + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function *range(start: number, end: number): IterableIterator { + for (let i = start; i < end; i++) { + yield i; + } +} + + +/** + * Retrieve the `index`-th item from the `array`. + * + * @remarks + * This is a fill for `Array.prototype.at`. + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/at + * + * @param array - to get the element from + * @param index - zero-based index of the `array`. Negative index counts back. + * @returns item, or `undefined` if `index` out-of-bound + * @typeParam T - type of the array items + */ +export function at( + array: Array, + index: number +) { + if (index < 0) { + return array[array.length + index]; + } else { + return array[index]; + } +} + + +/** + * Set the `index`-th item of the `array` to the `value`. + * + * @remarks + * Unlike the plain assignment, we do not grow the array on an out-of-bound + * `index`, but throw so that we follow the semantics of the meta-model. + * + * @param array - to set the element in + * @param index - zero-based index of the `array`. Negative index counts back. + * @param value - to be set + * @throws {@link RangeError} if `index` out-of-bound + * @typeParam T - type of the array items + */ +export function setAt( + array: Array, + index: number, + value: T +): void { + const resolved = index < 0 ? array.length + index : index; + if (resolved < 0 || resolved >= array.length) { + throw new RangeError( + `The index ${index} is out of bound ` + + `for the array of length ${array.length}` + ); + } + array[resolved] = value; +} + + +/** + * Check that all the values of the iterable are `true`. + * + * @param iterable - to iterate over + * @returns `true` if all values in `iterable` are set + */ +export function every( + iterable: Iterable +): boolean { + // NOTE (mristin): + // We introduce this function so that we can keep the constraint verification + // purely functional. Unfortunately, `every` and `some` are only available + // in arrays and not in `IterableIterator`. + + for (const item of iterable) { + if (!item) { + return false; + } + } + + return true; +} + + +/** + * Check that at least one value of the iterable is `true`. + * + * @param iterable - to iterate over + * @returns `true` if at least one value in `iterable` is set + */ +export function some( + iterable: Iterable +): boolean { + // NOTE (mristin): + // We introduce this function so that we can keep the constraint verification + // purely functional. Unfortunately, `every` and `some` are only available + // in arrays and not in `IterableIterator`. + + for (const item of iterable) { + if (item) { + return true; + } + } + + return false; +} + + +/** + * Map the items of an iterable. + * + * @param iterable - to be mapped + * @param mappingFunction - to be applied on `iterable` + * @returns mapped items of `iterable` + * @typeParam S - type of an item of the `iterable` + * @typeParam T - type of the transformed item of the `iterable` + */ +export function *map( + iterable: Iterable, + mappingFunction: (item: S) => T +): IterableIterator { + // NOTE (mristin): + // We introduce this function so that we can keep the constraint verification + // purely functional. + + for (const item of iterable) { + yield mappingFunction(item); + } +} + + +/** + * Represent either a result, or an error. + * + * @typeParam ValueT - type of the resulting value + * @typeParam ErrorT - type of the error + */ +export class Either { + /** + * value if something successful + */ + readonly value: ValueT | null; + + /** + * error if something failed + */ + readonly error: ErrorT | null; + + /** + * Assert that value is set and return it. + * + * @returns {@link value}, or throw if `null` + */ + mustValue(): ValueT { + if (this.value === null) { + throw new Error("Expected value to be set, but it was null"); + } + return this.value; + } + + constructor(value: ValueT | null, error: ErrorT | null) { + if (value === null && error === null) { + throw new Error("Unexpected both value and error null in an Either"); + } + + if (value !== null && error !== null) { + throw new Error("Unexpected both value and error non-null in an Either"); + } + + this.value = value; + this.error = error; + } +} + + +const BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +const BASE64_LOOKUP = new Uint8Array(256); + +// NOTE (mristin): +// Initialize to 255 so that we can detect invalid values in the input during decoding. +for (let i = 0; i < BASE64_LOOKUP.length; i++) { + BASE64_LOOKUP[i] = 255; +} + +// NOTE (mristin): +// Initialize valid values to the corresponding decoding points. +for (let i = 0; i < BASE64_CHARS.length; i++) { + BASE64_LOOKUP[BASE64_CHARS.charCodeAt(i)] = i; +} + +/** + * Encode a byte array in base64. + * + * @remarks + * We provide our own implementation so that we do not run into compatibility + * issues with node.js, different browsers etc. + * See: + * https://stackoverflow.com/questions/21797299/convert-base64-string-to-arraybuffer + * + * @param bytes - to be encoded + * @returns `bytes` encoded as base64 text + */ +export function base64Encode(bytes: Uint8Array): string { + // NOTE (mristin): + // This implementation is vaguely based on: + // https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js, + // https://github.com/niklasvh/base64-arraybuffer/blob/master/src/index.ts and + // https://github.com/beatgammit/base64-js/blob/master/index.js. + + // NOTE (mristin): + // We assume that string concatenation is actually *faster* than joining an array + // of strings, see: + // https://stackoverflow.com/questions/51185/are-javascript-strings-immutable-do-i-need-a-string-builder-in-javascript + + if (bytes.length === 0) { + return ""; + } + + let encoded = ''; + const len = bytes.length; + + for (let i = 0; i < len; i += 3) { + encoded += BASE64_CHARS[bytes[i] >> 2]; + encoded += BASE64_CHARS[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; + encoded += BASE64_CHARS[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; + encoded += BASE64_CHARS[bytes[i + 2] & 63]; + } + + // NOTE (mristin): + // We assume here that `substring` will be optimized for cases where we do not keep + // the original reference to the string. We tested a bit with + // https://www.measurethat.net/. + + if (len % 3 === 2) { + encoded = encoded.substring(0, encoded.length - 1) + '='; + } else if (len % 3 === 1) { + encoded = encoded.substring(0, encoded.length - 2) + '=='; + } else { + // No padding is necessary. + } + + return encoded; +} + + +/** + * Decode a base64-encoded byte array. + * + * @remarks + * We provide our own implementation so that we do not run into compatibility + * issues with node.js, different browsers etc. + * See: + * https://stackoverflow.com/questions/21797299/convert-base64-string-to-arraybuffer + * + * @param text - to be decoded + * @returns either the array or an error, if `text` is not a valid base64 encoding + */ +export function base64Decode(text: string): Either { + // NOTE (mristin): + // This implementation is vaguely based on: + // https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js, + // https://github.com/niklasvh/base64-arraybuffer/blob/master/src/index.ts and + // https://github.com/beatgammit/base64-js/blob/master/index.js. + + const len = text.length; + let lenWoPad = len; + + // NOTE (mristin): + // Some implementations forget the padding, so we try to be robust and check + // for the padding manually. + let bytesLength = text.length * 0.75; + if (text[len - 1] === '=') { + bytesLength--; + lenWoPad--; + if (text[len - 2] === '=') { + bytesLength--; + lenWoPad--; + } + } + + const bytes = new Uint8Array(bytesLength); + + const base64LookupLen = BASE64_LOOKUP.length; + + let pointer = 0; + + for (let i = 0; i < len; i += 4) { + // NOTE (mristin): + // Admittedly, this is very verbose code, but we want to be efficient, so we + // opted for performance over readability here. + + const charCode0 = text.charCodeAt(i); + if (charCode0 >= base64LookupLen) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i}: ${text[i]} (code: ${charCode0})` + ); + } + const encoded0 = BASE64_LOOKUP[charCode0]; + if (encoded0 === 255) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i}: ${text[i]} (code: ${charCode0})` + ); + } + + const charCode1 = text.charCodeAt(i + 1); + if (charCode1 >= base64LookupLen) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 1}: ${text[i + 1]} (code: ${charCode1})` + ); + } + const encoded1 = BASE64_LOOKUP[charCode1]; + if (encoded1 === 255) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 1}: ${text[i + 1]} (code: ${charCode1})` + ); + } + + // We map padding to 65, which is the value of "A". + const charCode2 = i + 2 < lenWoPad ? text.charCodeAt(i + 2) : 65; + if (charCode2 >= base64LookupLen) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 2}: ${text[i + 2]} (code: ${charCode2})` + ); + } + const encoded2 = BASE64_LOOKUP[charCode2]; + if (encoded2 === 255) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 2}: ${text[i + 2]} (code: ${charCode2})` + ); + } + + // We map padding to 65, which is the value of "A". + const charCode3 = i + 3 < lenWoPad ? text.charCodeAt(i + 3) : 65; + if (charCode3 >= base64LookupLen) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 3}: ${text[i + 3]} (code: ${charCode3})` + ); + } + const encoded3 = BASE64_LOOKUP[charCode3]; + if (encoded3 === 255) { + return new Either( + null, + "Expected a valid character from base64-encoded string, " + + `but got at index ${i + 3}: ${text[i + 3]} (code: ${charCode3})` + ); + } + + bytes[pointer] = (encoded0 << 2) | (encoded1 >> 4); + pointer++; + + bytes[pointer] = ((encoded1 & 15) << 4) | (encoded2 >> 2); + pointer++; + + bytes[pointer] = ((encoded2 & 3) << 6) | (encoded3 & 63); + pointer++; + } + +// NOTE (mristin): +// We expect Uint8Array to silently ignore writes outside of the buffer, +// but we still want to check here in case the underlying platform was flaky about it. + if (bytes.length !== bytesLength) { + throw new Error( + `Expected bytes to have length ${bytesLength}, but got ${bytes.length}` + ); + } + + return new Either(bytes, null); +} + + +/** + * Encode a byte array in base64url. + * + * This encoding differs from base64 in so far that the characters `+` and `/` are + * replaced with `-` and `_` and the padding (`=`) is removed, so that the encoded + * value can be inserted into URIs without escaping. + * + * See RFC 4648 for more details: + * https://www.rfc-editor.org/rfc/rfc4648#section-5 + * + * @param bytes - to be encoded + * @returns `bytes` encoded as base64url text + */ +export function base64UrlEncode(bytes: Uint8Array): string { + return base64Encode(bytes) + .replace( + /[+/=]/g, + m => + m === '+' ? '-' : + m === '/' ? '_' : + '' // The padding `=` is removed. + ); +} + + +/** + * Decode a base64url-encoded byte array. + * + * This encoding differs from base64 in so far that the characters `+` and `/` are + * replaced with `-` and `_` and the padding (`=`) is removed, so that the encoded + * value can be inserted into URIs without escaping. + * + * @param text - to be decoded + * @returns either the array or an error, if `text` is not a valid base64url encoding + */ +export function base64UrlDecode(text: string): Either { + let base64Text = text.replace(/[-_]/g, m => m === '-' ? '+' : '/'); + const pad = base64Text.length % 4; + if (pad > 0) { + base64Text += '='.repeat(4 - pad); + } + + return base64Decode(base64Text); +} + + +/** + * Check whether a surrogate pair starts at `offset` in `text`. + * + * @param text - to be inspected + * @param offset - in UTF-16 code units + * @returns whether a surrogate pair starts at `offset` + */ +function isSurrogatePairAt(text: string, offset: number): boolean { + if (offset + 1 >= text.length) { + return false; + } + + const high = text.charCodeAt(offset); + const low = text.charCodeAt(offset + 1); + return high >= 0xd800 && high <= 0xdbff && low >= 0xdc00 && low <= 0xdfff; +} + + +/** + * Count the characters of `text` between the UTF-16 offsets `startOffset` and + * `endOffset`. + * + * @param text - to be inspected + * @param startOffset - in UTF-16 code units, inclusive + * @param endOffset - in UTF-16 code units, exclusive + * @returns the number of characters (code points) + */ +function countCharacters( + text: string, + startOffset: number, + endOffset: number +): number { + let count = 0; + let offset = startOffset; + while (offset < endOffset) { + offset += isSurrogatePairAt(text, offset) ? 2 : 1; + count++; + } + + return count; +} + + +/** + * Compute the UTF-16 offset of the character at `position` in `text`. + * + * @param text - to be inspected + * @param position - of the character, in characters (code points) + * @returns the offset in UTF-16 code units + */ +function offsetOf(text: string, position: number): number { + let offset = 0; + for (let i = 0; i < position; i++) { + offset += isSurrogatePairAt(text, offset) ? 2 : 1; + } + + return offset; +} + + +/** + * Resolve `position` in a string of `length` as Python does in slicing. + * + * @remarks + * A negative position counts from the end, and the positions out of range are + * clamped to the string. + * + * @param position - to be resolved + * @param length - of the string + * @returns the resolved position within `[0, length]` + */ +function resolvePosition(position: number, length: number): number { + if (position < 0) { + return Math.max(position + length, 0); + } + + return Math.min(position, length); +} + + +/** + * Count the characters (code points) of `text`. + * + * @remarks + * We follow the Python implementation of `len`, since Python is the language of + * the meta-model specifications. Hence, a character beyond the Basic + * Multilingual Plane counts as one, unlike in `text.length`. + * + * @param text - to be measured + * @returns the number of characters + */ +export function lenStr(text: string): number { + return countCharacters(text, 0, text.length); +} + + +/** + * Slice `text` from `start` up to `end`, exclusive. + * + * @remarks + * We follow the Python implementation of slicing, since Python is + * the language of the meta-model specifications. Hence, the positions count + * the characters (code points), a negative position counts from the end, + * the positions out of range are clamped to the string, and the slice is empty + * if `start` is not before `end`. + * + * @param text - to be sliced + * @param start - of the slice, inclusive + * @param end - of the slice, exclusive; if not given, the length of `text` + * @returns the slice + */ +export function sliceStr(text: string, start: number, end?: number): string { + const length = lenStr(text); + const theStart = resolvePosition(start, length); + const theEnd = end === undefined ? length : resolvePosition(end, length); + + if (theStart >= theEnd) { + return ""; + } + + return text.substring(offsetOf(text, theStart), offsetOf(text, theEnd)); +} + + +/** + * Find the first `sub` in `text` from `start` on. + * + * @remarks + * We follow the Python implementation of `str.find`, since Python is + * the language of the meta-model specifications. Hence, the positions count + * the characters (code points), a negative `start` counts from the end, and + * a `start` beyond the end of `text` gives -1. + * + * @param text - to be searched in + * @param sub - to be searched for + * @param start - of the search; if not given, the beginning of `text` + * @returns the position of `sub` in `text`, or -1 if `sub` could not be found + */ +export function findStr(text: string, sub: string, start = 0): number { + const length = lenStr(text); + const theStart = start < 0 ? Math.max(start + length, 0) : start; + if (theStart > length) { + return -1; + } + + const startOffset = offsetOf(text, theStart); + const offset = text.indexOf(sub, startOffset); + if (offset === -1) { + return -1; + } + + return theStart + countCharacters(text, startOffset, offset); +} + + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/constants.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/constants.ts new file mode 100644 index 000000000..0107ee495 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/constants.ts @@ -0,0 +1,15 @@ +/** + * Provide constant values of the meta-model. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import * as OurTypes from "./types"; + +/* eslint-enable @typescript-eslint/no-unused-vars */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/index.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/index.ts new file mode 100644 index 000000000..a954b9203 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/index.ts @@ -0,0 +1,23 @@ +/** + * Provide dummy SDK. + * + * @packageDocumentation + */ + + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + + +export * as common from "./common"; +export * as constants from "./constants"; +export * as jsonization from "./jsonization"; +export * as xmlcommon from "./xmlcommon"; +export * as xmlization from "./xmlization"; +export * as stringification from "./stringification"; +export * as types from "./types"; +export * as verification from "./verification"; + + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/jsonization.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/jsonization.ts new file mode 100644 index 000000000..9329b5ee1 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/jsonization.ts @@ -0,0 +1,1459 @@ +/** + * Provide de/serialization of meta-model classes to/from JSON. + * + * We can not use one-pass deserialization for JSON since the object + * properties do not have fixed order, and hence we can not read + * `modelType` property ahead of the remaining properties. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurCommon from "./common"; +import * as OurTypes from "./types"; +import * as OurStringification from "./stringification"; + +export type JsonValue = string | number | boolean | JsonObject | JsonArray; + +export type JsonArray = Iterable; +export type JsonObject = { [prop: string]: JsonValue }; + +// NOTE (mristin): +// The SDK defines three of these path vocabularies: this one, the one in +// the `jsonization` module, and the one in the `xmlcommon` module. They look +// alike, and it is tempting to merge them, but they are not interchangeable. +// +// Each of them points into a different thing: +// +// * This one points into the instances which you built, so a property segment +// holds a class of the meta-model. +// * The jsonization's points into the JSON-able structure being read or +// written, so a property segment holds the JSON-able object instead: while +// a document is being parsed, the instance which the property would belong +// to does not exist yet. +// * The xmlcommon's points into the XML document, where there are no +// properties at all, only elements, so it names an element instead. It is +// also the only one whose segments carry no back-pointer, as the reading is +// a single pass over a token stream and the element is gone by the time +// the error comes back out. +// +// The three also render differently: a path into the instances is +// a TypeScript access expression, a path into a JSON-able structure starts at +// the root of the document and carries no leading dot, and a path into an XML +// document is a relative XPath. +// +// Merging them would mean either dropping the back-pointers, which have been +// part of the public API of this SDK since before these modules were split +// apart, or defining a single path over the union of all the segment kinds -- +// in which case every consumer would have to handle segments which can never +// occur in its world. Three small vocabularies which each say exactly what +// they can say cost less than one large one which lies about its range. + +/** + * Represent a property on a path to the erroneous value. + */ +export class PropertySegment { + /** + * Instance that contains the property + */ + readonly instance: JsonObject; + + /** + * Name of the property + */ + readonly name: string; + + constructor(instance: JsonObject, name: string) { + this.instance = instance; + this.name = name; + } +} + +/** + * Represent an index access on a path to the erroneous value. + */ +export class IndexSegment { + /** + * Container that contains the item + */ + readonly container: JsonArray; + + /** + * Index of the item + */ + readonly index: number; + + constructor(container: JsonArray, index: number) { + if (!Number.isInteger(index)) { + throw new Error(`Expected an integer for the index, but got: ${index}`); + } + + this.container = container; + this.index = index; + } +} + +/** + * Represent a member of an open JSON-able object on a path to the erroneous + * value. + * + * @remarks + * + * Unlike a {@link PropertySegment}, which names a property of one of our + * classes, a key names a member of an open JSON-able object. It is known only + * at run time, and can be any string at all, so it is always rendered as + * a subscript. + */ +export class KeySegment { + /** + * Object that contains the value at {@link key} + */ + readonly object: JsonObject; + + /** + * Key of the value + */ + readonly key: string; + + constructor(object: JsonObject, key: string) { + this.object = object; + this.key = key; + } +} + +export type Segment = PropertySegment | IndexSegment | KeySegment; + +/** + * Match the property names which a path writes after a dot; the other names + * are written in brackets as JSON strings, *e.g.*, `["$eq"]`. + */ +const VARIABLE_NAME_RE = /^[a-zA-Z_][a-zA-Z_0-9]*$/; + +/** + * Represent the relative path to the erroneous value. + */ +export class Path { + private readonly _segments = new Array(); + + /** + * Get the segments of the path. + */ + segments(): Array { + return this._segments; + } + + /** + * Insert the `segment` in front of the {@link segments}. + * + * @param segment - segment to be prepended to {@link segments} + */ + prepend(segment: Segment): void { + this._segments.unshift(segment); + } + + toString(): string { + if (this._segments.length === 0) { + return ""; + } + + const parts = new Array(); + + let segment = this._segments[0]; + + if (segment instanceof PropertySegment) { + parts.push( + VARIABLE_NAME_RE.test(segment.name) + ? segment.name + : `[${JSON.stringify(segment.name)}]` + ); + } else if (segment instanceof IndexSegment) { + parts.push(`[${segment.index}]`); + } else if (segment instanceof KeySegment) { + parts.push(`[${JSON.stringify(segment.key)}]`); + } else { + throw new Error(`Unexpected segment: ${segment}`); + } + + for (let i = 1; i < this._segments.length; i++) { + segment = this._segments[i]; + if (segment instanceof PropertySegment) { + parts.push( + VARIABLE_NAME_RE.test(segment.name) + ? `.${segment.name}` + : `[${JSON.stringify(segment.name)}]` + ); + } else if (segment instanceof IndexSegment) { + parts.push(`[${segment.index}]`); + } else if (segment instanceof KeySegment) { + parts.push(`[${JSON.stringify(segment.key)}]`); + } else { + throw new Error(`Unexpected segment: ${segment}`); + } + } + + return parts.join(""); + } +} + +// region De-serialization + +/** + * Signal that the JSON de-serialization could not be performed. + */ +export class DeserializationError { + /** + * Human-readable explanation of the error + */ + readonly message: string; + + /** + * Relative path to the erroneous value + */ + readonly path: Path; + + constructor(message: string, path: Path | null = null) { + this.message = message; + this.path = path ?? new Path(); + } +} + +/** + * Create an error as {@link common.Either}. + * + * @param message - human-readable explanation of the error + * @returns An {@link common.Either } with the error set + * @typeParam T - type of the value if there had been no error + */ +function newDeserializationError( + message: string +): OurCommon.Either { + return new OurCommon.Either( + null, + new DeserializationError(message) + ); +} + +/** + * Check that `jsonable` looks like a JSON object, without parsing it. + * + * @param jsonable - to be checked + * @returns error, if any + */ +function checkIsJsonObject(jsonable: JsonValue): DeserializationError | null { + if (jsonable === null) { + return new DeserializationError( + "Expected a JSON object, but got null" + ); + } + if (Array.isArray(jsonable)) { + return new DeserializationError( + "Expected a JSON object, but got a JSON array" + ); + } + if (typeof jsonable !== "object") { + return new DeserializationError( + `Expected a JSON object, but got: ${typeof jsonable}` + ); + } + + return null; +} + +/** + * Extract the `modelType` property of `jsonObject`. + * + * @param jsonObject - to be inspected + * @returns the model type, or an error + */ +function extractModelType( + jsonObject: JsonObject +): OurCommon.Either { + const modelType = jsonObject["modelType"]; + if (modelType === undefined) { + return newDeserializationError( + "The required property 'modelType' is missing" + ); + } + if (typeof modelType !== "string") { + // NOTE (mristin): + // A model type of a wrong type is reported at the property, as the type + // errors of all the other properties are, and as in the other SDKs. + const error = new DeserializationError( + `Expected the property modelType to be a string, ` + + `but got: ${typeof modelType}` + ); + error.path.prepend(new PropertySegment(jsonObject, "modelType")); + return new OurCommon.Either(null, error); + } + + return new OurCommon.Either(modelType, null); +} + +/** + * Check that the `modelType` property of `jsonObject` is `expected`. + * + * @param jsonObject - to be inspected + * @param expected - expected model type + * @returns error, if any + */ +function checkModelType( + jsonObject: JsonObject, + expected: string +): DeserializationError | null { + const modelTypeOrError = extractModelType(jsonObject); + if (modelTypeOrError.error !== null) { + return modelTypeOrError.error; + } + + const modelType = modelTypeOrError.mustValue(); + if (modelType !== expected) { + return new DeserializationError( + `Expected model type '${expected}', ` + + `but got: ${modelType}` + ); + } + + return null; +} + +/** + * Check that `jsonable` looks like an iterable which can be parsed + * item-by-item, without consuming it. + * + * @param jsonable - to be checked + * @returns error, if any + */ +function checkIsIterable(jsonable: JsonValue): DeserializationError | null { + if (jsonable === null) { + return new DeserializationError( + "Expected an iterable, but got null" + ); + } + if (typeof jsonable !== "object") { + return new DeserializationError( + `Expected an iterable, but got: ${typeof jsonable}` + ); + } + if (typeof jsonable[Symbol.iterator] !== "function") { + return new DeserializationError( + "Expected an iterable with iterator function, " + + `but got iterator of type: ${typeof jsonable[Symbol.iterator]}` + ); + } + + return null; +} + +/** + * Parse `jsonable` as an array, and every one of its items with `parseItem`. + * + * @param jsonable - to be parsed item-by-item + * @param parseItem - to parse a single item of `jsonable` + * @returns parsed items, or an error + * @typeParam T - type of a single parsed item + */ +function parseArray( + jsonable: JsonValue, + parseItem: ( + jsonableItem: JsonValue + ) => OurCommon.Either +): OurCommon.Either, DeserializationError> { + const iterableError = checkIsIterable(jsonable); + if (iterableError !== null) { + return new OurCommon.Either, DeserializationError>( + null, + iterableError + ); + } + + const iterable = >jsonable; + + const items = new Array(); + let i = 0; + for (const jsonableItem of iterable) { + const itemOrError = parseItem(jsonableItem); + if (itemOrError.error !== null) { + itemOrError.error.path.prepend(new IndexSegment(iterable, i)); + return new OurCommon.Either, DeserializationError>( + null, + itemOrError.error + ); + } + items.push(itemOrError.mustValue()); + i++; + } + return new OurCommon.Either, DeserializationError>(items, null); +} + +/** + * Parse `jsonable` as a boolean. + * + * @param jsonable - to be parsed + * @returns parsed boolean value, or an error + */ +function booleanFromJsonable( + jsonable: JsonValue +): OurCommon.Either { + // `typeof` seems to be optimized these days, so we use it instead of + // literal comparison, see: + // https://stackoverflow.com/questions/61786250/is-typeof-faster-than-literal-comparison + + if (jsonable === null) { + return newDeserializationError( + "Expected a boolean, but got null" + ); + } + if (typeof jsonable !== "boolean") { + return newDeserializationError( + `Expected a boolean, but got ${typeof jsonable}` + ); + } + + return new OurCommon.Either(jsonable, null); +} + +/** + * Parse `jsonable` as an integer. + * + * @param jsonable - to be parsed + * @returns parsed integer value, or an error + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function integerFromJsonable( + jsonable: JsonValue +): OurCommon.Either { + if (jsonable === null) { + return newDeserializationError( + "Expected an integer number, but got null" + ); + } + if (typeof jsonable !== "number") { + return newDeserializationError( + `Expected an integer number, but got: ${typeof jsonable}` + ); + } + + if (!Number.isInteger(jsonable)) { + return newDeserializationError( + `Expected an integer number, but got: ${jsonable}` + ); + } + + return new OurCommon.Either(jsonable, null); +} + +/** + * Parse `jsonable` as a number. + * + * @param jsonable - to be parsed + * @returns parsed numeric value, or an error + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function numberFromJsonable( + jsonable: JsonValue +): OurCommon.Either { + if (jsonable === null) { + return newDeserializationError( + "Expected a number, but got null" + ); + } + if (typeof jsonable !== "number") { + return newDeserializationError( + `Expected a number, but got: ${typeof jsonable}` + ); + } + + // NOTE (mristin): + // JSON knows neither an infinity nor a not-a-number, so a conformant parser + // can never give us one. The caller can still hand us a JSON-able which has + // been constructed programmatically, so we have to check here. + if (!Number.isFinite(jsonable)) { + return newDeserializationError( + `Expected a finite number, but got: ${jsonable}` + ); + } + + return new OurCommon.Either(jsonable, null); +} + +/** + * Parse `jsonable` as a string. + * + * @param jsonable - to be parsed + * @returns parsed string value, or an error + */ +function stringFromJsonable( + jsonable: JsonValue +): OurCommon.Either { + if (jsonable === null) { + return newDeserializationError( + "Expected a string, but got null" + ); + } + if (typeof jsonable !== "string") { + return newDeserializationError( + `Expected a string, but got: ${typeof jsonable}` + ); + } + + return new OurCommon.Either(jsonable, null); +} + +/** + * Parse `jsonable` as a byte array. + * + * @param jsonable - to be parsed + * @returns parsed byte array, or an error + */ +function bytesFromJsonable( + jsonable: JsonValue +): OurCommon.Either { + if (jsonable === null) { + return newDeserializationError( + "Expected a base64-encoded string, but got null" + ); + } + if (typeof jsonable !== "string") { + return newDeserializationError( + `Expected a base64-encoded string, but got: ${typeof jsonable}` + ); + } + + const either = OurCommon.base64Decode(jsonable); + if (either.error !== null) { + return newDeserializationError(either.error); + } + return new OurCommon.Either( + either.mustValue(), null + ); +} + +/** + * Parse `jsonable` as an instance + * of {@link types!IItem}. + * + * @param jsonable - to be parsed + * @returns parsed instance, or error if `jsonable` is invalid + */ +export function itemFromJsonable( + jsonable: JsonValue +): OurCommon.Either< + OurTypes.IItem, + DeserializationError +> { + const objectError = checkIsJsonObject(jsonable); + if (objectError !== null) { + return new OurCommon.Either< + OurTypes.IItem, + DeserializationError + >( + null, + objectError + ); + } + const jsonObject = jsonable; + + const modelTypeOrError = extractModelType(jsonObject); + if (modelTypeOrError.error !== null) { + return new OurCommon.Either< + OurTypes.IItem, + DeserializationError + >( + null, + modelTypeOrError.error + ); + } + + const modelType = modelTypeOrError.mustValue(); + + switch (modelType) { + case "SomeItem": + return parsePropertiesOfSomeItem(jsonObject); + + case "SpecialItem": + return parsePropertiesOfSpecialItem(jsonObject); + + default: + return newDeserializationError( + `Unexpected model type for IItem: ${modelType}` + ); + } +} + +/** + * Parse the properties of an instance + * of {@link types!SomeItem} from `jsonObject`. + * + * The `modelType` is expected to have been already verified by the caller, + * and is therefore skipped here. + * + * @param jsonObject - JSON object to be parsed + * @returns parsed instance of {@link types!SomeItem}, + * or an error if any + */ +function parsePropertiesOfSomeItem( + jsonObject: JsonObject +): OurCommon.Either< + OurTypes.SomeItem, + DeserializationError +> { + let theName: string | null = null; + let theNickname: string | null = null; + + for (const key in jsonObject) { + const jsonableValue = jsonObject[key]; + + let propertyError: DeserializationError | null = null; + switch (key) { + case "name": { + const parsed = stringFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theName = parsed.value; + break; + } + + case "nickname": { + const parsed = stringFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theNickname = parsed.value; + break; + } + + case "modelType": { + // The model type has already been verified by the caller. + break; + } + + // NOTE (mristin): + // Since we conflate here a JavaScript object with a JSON object, we ignore + // properties which we do not know how to de-serialize and assume they are + // related to the *JavaScript* properties of the object or `Object` prototype. + default: { + continue; + } + } + + if (propertyError !== null) { + propertyError.path.prepend( + new PropertySegment(jsonObject, key) + ); + return new OurCommon.Either< + OurTypes.SomeItem, + DeserializationError + >( + null, + propertyError + ); + } + } + + if (theName === null) { + return newDeserializationError< + OurTypes.SomeItem + >( + "The required property 'name' is missing" + ); + } + + return new OurCommon.Either< + OurTypes.SomeItem, + DeserializationError + >( + new OurTypes.SomeItem( + theName, + theNickname + ), + null + ); +} + +/** + * Parse `jsonable` as an instance + * of {@link types!ISomeItem}. + * + * @param jsonable - to be parsed + * @returns parsed instance, or error if `jsonable` is invalid + */ +export function someItemFromJsonable( + jsonable: JsonValue +): OurCommon.Either< + OurTypes.ISomeItem, + DeserializationError +> { + const objectError = checkIsJsonObject(jsonable); + if (objectError !== null) { + return new OurCommon.Either< + OurTypes.ISomeItem, + DeserializationError + >( + null, + objectError + ); + } + const jsonObject = jsonable; + + const modelTypeOrError = extractModelType(jsonObject); + if (modelTypeOrError.error !== null) { + return new OurCommon.Either< + OurTypes.ISomeItem, + DeserializationError + >( + null, + modelTypeOrError.error + ); + } + + const modelType = modelTypeOrError.mustValue(); + + switch (modelType) { + case "SpecialItem": + return parsePropertiesOfSpecialItem(jsonObject); + + case "SomeItem": + return parsePropertiesOfSomeItem(jsonObject); + + default: + return newDeserializationError( + `Unexpected model type for ISomeItem: ${modelType}` + ); + } +} + +/** + * Parse the properties of an instance + * of {@link types!SpecialItem} from `jsonObject`. + * + * The `modelType` is expected to have been already verified by the caller, + * and is therefore skipped here. + * + * @param jsonObject - JSON object to be parsed + * @returns parsed instance of {@link types!SpecialItem}, + * or an error if any + */ +function parsePropertiesOfSpecialItem( + jsonObject: JsonObject +): OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError +> { + let theName: string | null = null; + let theNickname: string | null = null; + let theLevel: number | null = null; + + for (const key in jsonObject) { + const jsonableValue = jsonObject[key]; + + let propertyError: DeserializationError | null = null; + switch (key) { + case "name": { + const parsed = stringFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theName = parsed.value; + break; + } + + case "nickname": { + const parsed = stringFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theNickname = parsed.value; + break; + } + + case "level": { + const parsed = integerFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theLevel = parsed.value; + break; + } + + case "modelType": { + // The model type has already been verified by the caller. + break; + } + + // NOTE (mristin): + // Since we conflate here a JavaScript object with a JSON object, we ignore + // properties which we do not know how to de-serialize and assume they are + // related to the *JavaScript* properties of the object or `Object` prototype. + default: { + continue; + } + } + + if (propertyError !== null) { + propertyError.path.prepend( + new PropertySegment(jsonObject, key) + ); + return new OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError + >( + null, + propertyError + ); + } + } + + if (theName === null) { + return newDeserializationError< + OurTypes.SpecialItem + >( + "The required property 'name' is missing" + ); + } + + if (theLevel === null) { + return newDeserializationError< + OurTypes.SpecialItem + >( + "The required property 'level' is missing" + ); + } + + return new OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError + >( + new OurTypes.SpecialItem( + theName, + theLevel, + theNickname + ), + null + ); +} + +/** + * Parse an instance of {@link types!SpecialItem} from the JSON-able + * structure `jsonable`. + * + * @param jsonable - structure to be parsed + * @returns parsed instance of {@link types!SpecialItem}, + * or an error if any + */ +export function specialItemFromJsonable( + jsonable: JsonValue +): OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError +> { + const objectError = checkIsJsonObject(jsonable); + if (objectError !== null) { + return new OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError + >( + null, + objectError + ); + } + const jsonObject = jsonable; + + const modelTypeError = checkModelType(jsonObject, "SpecialItem"); + if (modelTypeError !== null) { + return new OurCommon.Either< + OurTypes.SpecialItem, + DeserializationError + >( + null, + modelTypeError + ); + } + + return parsePropertiesOfSpecialItem(jsonObject); +} + +/** + * Parse the properties of an instance + * of {@link types!Container} from `jsonObject`. + * + * @param jsonObject - JSON object to be parsed + * @returns parsed instance of {@link types!Container}, + * or an error if any + */ +function parsePropertiesOfContainer( + jsonObject: JsonObject +): OurCommon.Either< + OurTypes.Container, + DeserializationError +> { + let theItems: Array | null = null; + let theSomeItems: Array | null = null; + let theSpecialItems: Array | null = null; + + for (const key in jsonObject) { + const jsonableValue = jsonObject[key]; + + let propertyError: DeserializationError | null = null; + switch (key) { + case "items": { + const parsed = parseArray( + jsonableValue, + itemFromJsonable + ); + propertyError = parsed.error; + theItems = parsed.value; + break; + } + + case "someItems": { + const parsed = parseArray( + jsonableValue, + someItemFromJsonable + ); + propertyError = parsed.error; + theSomeItems = parsed.value; + break; + } + + case "specialItems": { + const parsed = parseArray( + jsonableValue, + specialItemFromJsonable + ); + propertyError = parsed.error; + theSpecialItems = parsed.value; + break; + } + + // NOTE (mristin): + // Since we conflate here a JavaScript object with a JSON object, we ignore + // properties which we do not know how to de-serialize and assume they are + // related to the *JavaScript* properties of the object or `Object` prototype. + default: { + continue; + } + } + + if (propertyError !== null) { + propertyError.path.prepend( + new PropertySegment(jsonObject, key) + ); + return new OurCommon.Either< + OurTypes.Container, + DeserializationError + >( + null, + propertyError + ); + } + } + + if (theItems === null) { + return newDeserializationError< + OurTypes.Container + >( + "The required property 'items' is missing" + ); + } + + if (theSomeItems === null) { + return newDeserializationError< + OurTypes.Container + >( + "The required property 'someItems' is missing" + ); + } + + return new OurCommon.Either< + OurTypes.Container, + DeserializationError + >( + new OurTypes.Container( + theItems, + theSomeItems, + theSpecialItems + ), + null + ); +} + +/** + * Parse an instance of {@link types!Container} from the JSON-able + * structure `jsonable`. + * + * @param jsonable - structure to be parsed + * @returns parsed instance of {@link types!Container}, + * or an error if any + */ +export function containerFromJsonable( + jsonable: JsonValue +): OurCommon.Either< + OurTypes.Container, + DeserializationError +> { + const objectError = checkIsJsonObject(jsonable); + if (objectError !== null) { + return new OurCommon.Either< + OurTypes.Container, + DeserializationError + >( + null, + objectError + ); + } + const jsonObject = jsonable; + + return parsePropertiesOfContainer(jsonObject); +} + +/** + * Parse the properties of an instance + * of {@link types!Wrapper} from `jsonObject`. + * + * @param jsonObject - JSON object to be parsed + * @returns parsed instance of {@link types!Wrapper}, + * or an error if any + */ +function parsePropertiesOfWrapper( + jsonObject: JsonObject +): OurCommon.Either< + OurTypes.Wrapper, + DeserializationError +> { + let theContainer: OurTypes.Container | null = null; + + for (const key in jsonObject) { + const jsonableValue = jsonObject[key]; + + let propertyError: DeserializationError | null = null; + switch (key) { + case "container": { + const parsed = containerFromJsonable( + jsonableValue + ); + propertyError = parsed.error; + theContainer = parsed.value; + break; + } + + // NOTE (mristin): + // Since we conflate here a JavaScript object with a JSON object, we ignore + // properties which we do not know how to de-serialize and assume they are + // related to the *JavaScript* properties of the object or `Object` prototype. + default: { + continue; + } + } + + if (propertyError !== null) { + propertyError.path.prepend( + new PropertySegment(jsonObject, key) + ); + return new OurCommon.Either< + OurTypes.Wrapper, + DeserializationError + >( + null, + propertyError + ); + } + } + + if (theContainer === null) { + return newDeserializationError< + OurTypes.Wrapper + >( + "The required property 'container' is missing" + ); + } + + return new OurCommon.Either< + OurTypes.Wrapper, + DeserializationError + >( + new OurTypes.Wrapper( + theContainer + ), + null + ); +} + +/** + * Parse an instance of {@link types!Wrapper} from the JSON-able + * structure `jsonable`. + * + * @param jsonable - structure to be parsed + * @returns parsed instance of {@link types!Wrapper}, + * or an error if any + */ +export function wrapperFromJsonable( + jsonable: JsonValue +): OurCommon.Either< + OurTypes.Wrapper, + DeserializationError +> { + const objectError = checkIsJsonObject(jsonable); + if (objectError !== null) { + return new OurCommon.Either< + OurTypes.Wrapper, + DeserializationError + >( + null, + objectError + ); + } + const jsonObject = jsonable; + + return parsePropertiesOfWrapper(jsonObject); +} + +// endregion + +// region Serialization + +/** + * Signal that the JSON serialization could not be performed. + * + * The {@link SerializationError.path} points into the instance which was to be + * serialized, and *not* into a JSON document -- at the point of the failure, + * there is no document yet. For example, `.submodels[0].value` tells you that + * the serialization broke on `that.submodels[0].value`. + * + * Mind that this path is a plain string, unlike the structured + * {@link Path} of the de-serialization. A segment of the latter carries + * the JSON value it was read from, and while serializing there is no such + * value to carry. + */ +export class SerializationError extends Error { + private readonly _segments = new Array(); + + /** + * Render the path to the erroneous value as a TypeScript access expression. + */ + get path(): string { + return this._segments.join(""); + } + + /** + * Insert the access to the property `name` before the {@link path}. + */ + prependProperty(name: string): void { + this._segments.unshift(`.${name}`); + } + + /** + * Insert the access to the item at `index` before the {@link path}. + */ + prependIndex(index: number): void { + this._segments.unshift(`[${index}]`); + } + + /** + * Insert the access to the member `key` before the {@link path}. + * + * @remarks + * + * Unlike a property of one of our classes, a member of an open JSON-able + * object is known only at run time and can be any string at all, so it is + * always rendered as a subscript. + */ + prependKey(key: string): void { + this._segments.unshift(`[${JSON.stringify(key)}]`); + } +} + +/** + * Serialize `that` integer to a JSON-able value. + * + * Only the integers in the range [-2^53 + 1, 2^53 - 1] are serialized. Outside + * of it, an integer can not be exactly represented as a 64-bit floating-point + * number, which is what the JSON de-serializers of the other languages read + * a number into. + * + * @param that - integer to be serialized + * @returns `that`, unchanged + * @throws {@link SerializationError} if outside the range + */ +function integerToJsonable(that: number): number { + if (!Number.isFinite(that) || that < -9007199254740991 || that > 9007199254740991) { + throw new SerializationError( + `The integer can not be serialized to JSON as it is outside ` + + `the range [-2^53 + 1, 2^53 - 1]: ${that}` + ); + } + return that; +} + +/** + * Serialize `that` to a JSON-able representation. + * + * @param that - instance to be serialized + * @returns JSON-able representation + */ +function serializeSomeItem( + that: OurTypes.SomeItem +): JsonObject { + const jsonable: JsonObject = {}; + + // The property being serialized, for the path of a failure. + let prop = ""; + try { + prop = "name"; + jsonable["name"] = + that.name; + + if (that.nickname !== null) { + prop = "nickname"; + jsonable["nickname"] = + that.nickname; + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependProperty(prop); + } + throw error; + } + + jsonable["modelType"] = "SomeItem"; + + return jsonable; +} + +/** + * Serialize `that` to a JSON-able representation. + * + * @param that - instance to be serialized + * @returns JSON-able representation + */ +function serializeSpecialItem( + that: OurTypes.SpecialItem +): JsonObject { + const jsonable: JsonObject = {}; + + // The property being serialized, for the path of a failure. + let prop = ""; + try { + prop = "name"; + jsonable["name"] = + that.name; + + if (that.nickname !== null) { + prop = "nickname"; + jsonable["nickname"] = + that.nickname; + } + + prop = "level"; + jsonable["level"] = + integerToJsonable(that.level); + } catch (error) { + if (error instanceof SerializationError) { + error.prependProperty(prop); + } + throw error; + } + + jsonable["modelType"] = "SpecialItem"; + + return jsonable; +} + +/** + * Serialize `that` to a JSON-able representation. + * + * @param that - instance to be serialized + * @returns JSON-able representation + */ +function serializeContainer( + that: OurTypes.Container +): JsonObject { + const jsonable: JsonObject = {}; + + // The property being serialized, for the path of a failure. + let prop = ""; + try { + prop = "items"; + jsonable["items"] = + serialize_ListOf_IItem(that.items); + + prop = "someItems"; + jsonable["someItems"] = + serialize_ListOf_SomeItem(that.someItems); + + if (that.specialItems !== null) { + prop = "specialItems"; + jsonable["specialItems"] = + serialize_ListOf_SpecialItem(that.specialItems); + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependProperty(prop); + } + throw error; + } + + return jsonable; +} + +/** + * Serialize `that` to a JSON-able representation. + * + * @param that - instance to be serialized + * @returns JSON-able representation + */ +function serializeWrapper( + that: OurTypes.Wrapper +): JsonObject { + const jsonable: JsonObject = {}; + + // The property being serialized, for the path of a failure. + let prop = ""; + try { + prop = "container"; + jsonable["container"] = + serializeContainer(that.container); + } catch (error) { + if (error instanceof SerializationError) { + error.prependProperty(prop); + } + throw error; + } + + return jsonable; +} + +/** + * Serialize `that` to a JSON-able array. + * + * @param that - list to be serialized + * @returns JSON-able array + */ +function serialize_ListOf_IItem( + that: ReadonlyArray +): Array { + const result = new Array(that.length); + let i = 0; + try { + for (; i < that.length; i++) { + result[i] = serializeClass(that[i]); + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependIndex(i); + } + throw error; + } + return result; +} + +/** + * Serialize `that` to a JSON-able array. + * + * @param that - list to be serialized + * @returns JSON-able array + */ +function serialize_ListOf_SomeItem( + that: ReadonlyArray +): Array { + const result = new Array(that.length); + let i = 0; + try { + for (; i < that.length; i++) { + result[i] = serializeClass(that[i]); + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependIndex(i); + } + throw error; + } + return result; +} + +/** + * Serialize `that` to a JSON-able array. + * + * @param that - list to be serialized + * @returns JSON-able array + */ +function serialize_ListOf_SpecialItem( + that: ReadonlyArray +): Array { + const result = new Array(that.length); + let i = 0; + try { + for (; i < that.length; i++) { + result[i] = serializeSpecialItem(that[i]); + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependIndex(i); + } + throw error; + } + return result; +} + +/** + * Dispatch the serialization on the run-time type of an instance. + */ +class Serializer extends OurTypes.AbstractTransformer { + transformSomeItem( + that: OurTypes.SomeItem + ): JsonObject { + return serializeSomeItem(that); + } + + transformSpecialItem( + that: OurTypes.SpecialItem + ): JsonObject { + return serializeSpecialItem(that); + } + + transformContainer( + that: OurTypes.Container + ): JsonObject { + return serializeContainer(that); + } + + transformWrapper( + that: OurTypes.Wrapper + ): JsonObject { + return serializeWrapper(that); + } +} + +const SERIALIZER = new Serializer(); + +/** + * Serialize `that` to a JSON-able representation. + * + * Which JSON object that is, is decided by the run-time type of `that`, so this + * one function serves an abstract class, a concrete class with descendants and + * a named union alike. The de-serialization, which has to decide what to construct + * before it has read anything, needs a dispatcher per type instead. + * + * @param that - instance to be serialized + * @returns JSON-able representation + */ +function serializeClass(that: OurTypes.Class): JsonObject { + return that.transform(SERIALIZER); +} + +/** + * Convert `that` to a JSON-able structure. + * + * @param that - Model instance to be recursively converted to a JSON-able structure + * @returns + * JSON-able structure which can be further processed with, say, + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify|JSON.stringify}) + */ +export function toJsonable(that: OurTypes.Class): JsonObject { + return serializeClass(that); +} + +// endregion + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/stringification.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/stringification.ts new file mode 100644 index 000000000..9af7bc1a4 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/stringification.ts @@ -0,0 +1,88 @@ +/** + * De/serialize enumerations from and to string representations. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "./types"; + +const MODEL_TYPE_FROM_STRING = new Map([ + [ + "SomeItem", + OurTypes.ModelType.SomeItem + ], + [ + "SpecialItem", + OurTypes.ModelType.SpecialItem + ], + [ + "Container", + OurTypes.ModelType.Container + ], + [ + "Wrapper", + OurTypes.ModelType.Wrapper + ] +]); + +/** + * Parse `text` as a string representation of {@link types!ModelType}. + * + * @param text - string representation of {@link types!ModelType} + * @returns literal of {@link types!ModelType}, if valid, and `null` otherwise + */ +export function modelTypeFromString( + text: string +): OurTypes.ModelType | null { + const result = MODEL_TYPE_FROM_STRING.get(text); + return result !== undefined ? result : null; +} + +// NOTE (mristin): +// The literals of ModelType are consecutive integers starting at 0, +// so we index into an array instead of looking the text up in a map. +const MODEL_TYPE_TO_STRING: readonly string[] = [ + "SomeItem", + "SpecialItem", + "Container", + "Wrapper" +]; + +/** + * Translate {@link types!ModelType} to a string. + * + * @param value - to be stringified + * @returns string representation of {@link types!ModelType}, + * if `value` valid, and `null` otherwise + */ +export function modelTypeToString( + value: OurTypes.ModelType +): string | null { + const result = MODEL_TYPE_TO_STRING[value]; + return result !== undefined ? result : null; +} + +/** + * Translate {@link types!ModelType} to a string. + * + * @param value - to be stringified + * @returns string representation of {@link types!ModelType} + * @throws + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error|Error} + * if the `value` is invalid + */ +export function mustModelTypeToString( + value: OurTypes.ModelType +): string { + const result = MODEL_TYPE_TO_STRING[value]; + if (result === undefined) { + throw new Error( + `Invalid literal of ModelType: ${value}` + ); + } + return result; +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/types.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/types.ts new file mode 100644 index 000000000..35f0b6869 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/types.ts @@ -0,0 +1,1448 @@ +/** + * Test the variance of the containers passed as arguments. + * + * @remarks + * A read-only `Sequence[...]` argument of a verification function is generic if its + * items are instances of a class with descendants, so that the sequences of + * the descendants can be passed to it without a copy. The arguments of the methods + * and the mutable `List[...]` arguments remain invariant. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +/** + * Represent runtime model type of an instance. + */ +export enum ModelType { + SomeItem = 0, + SpecialItem = 1, + Container = 2, + Wrapper = 3 +} + +/** + * Iterate over the literals of {@link ModelType}. + * + * @remark + * TypeScript does not provide an elegant way to iterate over the literals, so + * this function helps you avoid common errors and pitfalls. + * + * @return iterator over the literals + */ +export function *overModelType ( +): Iterable { + // NOTE (mristin): + // We yield numbers instead of literals to avoid name lookups on platforms + // which do not provide JIT compilation of hot paths. + yield 0; // SomeItem + yield 1; // SpecialItem + yield 2; // Container + yield 3; // Wrapper +} + +/** + * Represent the most general class of the meta-model. + */ +export abstract class Class { + /** + * Indicate the runtime model type of an instance. + */ + abstract modelType(): ModelType; + + /** + * Iterate over all the instances referenced from this one. + */ + abstract descendOnce(): IterableIterator; + + /** + * Iterate recursively over all the instances referenced from this one. + */ + abstract descend(): IterableIterator; + + /** + * Dispatch the `visitor` on this instance. + * + * @param visitor - to be dispatched + */ + abstract accept(visitor: AbstractVisitor): void; + + /** + * Dispatch the `visitor` on this instance with `context`. + * + * @param visitor - to be dispatched + * @param context - of the visitation + * @typeParam ContextT - type of the visitation context + */ + abstract acceptWithContext( + visitor: AbstractVisitorWithContext, + context: ContextT + ): void; + + /** + * Dispatch the `transformer` on this instance. + * + * @param transformer - to be dispatched + * @return this instance transformed + * @typeParam T - type of the transformation result + */ + abstract transform(transformer: AbstractTransformer): T; + + /** + * Dispatch the `transformer` on this instance in `context`. + * + * @param transformer - to be dispatched + * @param context - of the transformation + * @return this instance transformed + * @typeParam T - type of the transformation result + */ + abstract transformWithContext( + transformer: AbstractTransformerWithContext, + context: ContextT + ): T; +} + +export interface IItem extends Class { + name: string; + + nickname: string | null; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ISomeItem + extends IItem { + // Intentionally empty. +} + +export class SomeItem + extends Class + implements ISomeItem { + /** + * Indicate the runtime model type of the instance. + */ + modelType(): ModelType { + // NOTE (mristin): + // We yield numbers instead of literals to avoid name lookups on platforms + // which do not provide JIT compilation of hot paths. + return 0; // SomeItem + } + + name: string; + + nickname: string | null; + + /** + * Iterate over the instances referenced from this instance. + * + * We do not recurse into the referenced instances. + * + * @returns Iterator over the referenced instances + */ + *descendOnce(): IterableIterator { + // No descendable properties + } + + /** + * Iterate recursively over the instances referenced from this instance. + * + * @returns Iterator over the referenced instances + */ + *descend(): IterableIterator { + // No descendable properties + } + + /** + * Dispatch `visitor` on this instance. + * + * @param visitor - to visit this instance + */ + accept(visitor: AbstractVisitor): void { + visitor.visitSomeItem(this); + } + + /** + * Dispatch `visitor` with `context` on this instance. + * + * @param visitor - to visit this instance + * @param context - to be passed along to the dispatched visitor method + * @typeParam ContextT - type of the context + */ + acceptWithContext( + visitor: AbstractVisitorWithContext, + context: ContextT + ) { + visitor.visitSomeItemWithContext(this, context); + } + + /** + * Dispatch the `transformer` on this instance. + * + * @param transformer - to transform this instance + * @returns transformation of this instance + * @paramType T - type of the transformation result + */ + transform(transformer: AbstractTransformer): T { + return transformer.transformSomeItem(this); + } + + /** + * Dispatch the `transformer` on this instance in `context`. + * + * @param transformer - to transform this instance + * @param context - to be passed along to the `transformer` + * @returns transformation of this instance + * @paramType T - type of the transformation result + * @paramType ContextT - type of the transformation context + */ + transformWithContext( + transformer: AbstractTransformerWithContext, + context: ContextT + ): T { + return transformer.transformSomeItemWithContext( + this, context + ); + } + + constructor( + name: string, + nickname: string | null = null + ) { + super(); + this.name = name; + this.nickname = nickname; + } +} + +export class SpecialItem + extends Class + implements ISomeItem { + /** + * Indicate the runtime model type of the instance. + */ + modelType(): ModelType { + // NOTE (mristin): + // We yield numbers instead of literals to avoid name lookups on platforms + // which do not provide JIT compilation of hot paths. + return 1; // SpecialItem + } + + name: string; + + nickname: string | null; + + level: number; + + /** + * Iterate over the instances referenced from this instance. + * + * We do not recurse into the referenced instances. + * + * @returns Iterator over the referenced instances + */ + *descendOnce(): IterableIterator { + // No descendable properties + } + + /** + * Iterate recursively over the instances referenced from this instance. + * + * @returns Iterator over the referenced instances + */ + *descend(): IterableIterator { + // No descendable properties + } + + /** + * Dispatch `visitor` on this instance. + * + * @param visitor - to visit this instance + */ + accept(visitor: AbstractVisitor): void { + visitor.visitSpecialItem(this); + } + + /** + * Dispatch `visitor` with `context` on this instance. + * + * @param visitor - to visit this instance + * @param context - to be passed along to the dispatched visitor method + * @typeParam ContextT - type of the context + */ + acceptWithContext( + visitor: AbstractVisitorWithContext, + context: ContextT + ) { + visitor.visitSpecialItemWithContext(this, context); + } + + /** + * Dispatch the `transformer` on this instance. + * + * @param transformer - to transform this instance + * @returns transformation of this instance + * @paramType T - type of the transformation result + */ + transform(transformer: AbstractTransformer): T { + return transformer.transformSpecialItem(this); + } + + /** + * Dispatch the `transformer` on this instance in `context`. + * + * @param transformer - to transform this instance + * @param context - to be passed along to the `transformer` + * @returns transformation of this instance + * @paramType T - type of the transformation result + * @paramType ContextT - type of the transformation context + */ + transformWithContext( + transformer: AbstractTransformerWithContext, + context: ContextT + ): T { + return transformer.transformSpecialItemWithContext( + this, context + ); + } + + constructor( + name: string, + level: number, + nickname: string | null = null + ) { + super(); + this.name = name; + this.nickname = nickname; + this.level = level; + } +} + +export class Container extends Class { + /** + * Indicate the runtime model type of the instance. + */ + modelType(): ModelType { + // NOTE (mristin): + // We yield numbers instead of literals to avoid name lookups on platforms + // which do not provide JIT compilation of hot paths. + return 2; // Container + } + + items: Array; + + someItems: Array; + + specialItems: Array | null; + + /** + * Yield from {@link specialItems} if it is set, or yield nothing. + */ + *overSpecialItemsOrEmpty(): IterableIterator { + if (this.specialItems !== null) { + yield * this.specialItems; + } + return; + } + + /** + * Count the `items`. + * + * @remarks + * + * The arguments of the methods are never generic, so only the sequences of + * {@link IItem} can be passed to it, but not the sequences of + * the descendants. + */ + countItems( + items: Array + ): number { + return items.length; + } + + /** + * Iterate over the instances referenced from this instance. + * + * We do not recurse into the referenced instances. + * + * @returns Iterator over the referenced instances + */ + *descendOnce(): IterableIterator { + yield * descend_Container(this, false); + } + + /** + * Iterate recursively over the instances referenced from this instance. + * + * @returns Iterator over the referenced instances + */ + *descend(): IterableIterator { + yield * descend_Container(this, true); + } + + /** + * Dispatch `visitor` on this instance. + * + * @param visitor - to visit this instance + */ + accept(visitor: AbstractVisitor): void { + visitor.visitContainer(this); + } + + /** + * Dispatch `visitor` with `context` on this instance. + * + * @param visitor - to visit this instance + * @param context - to be passed along to the dispatched visitor method + * @typeParam ContextT - type of the context + */ + acceptWithContext( + visitor: AbstractVisitorWithContext, + context: ContextT + ) { + visitor.visitContainerWithContext(this, context); + } + + /** + * Dispatch the `transformer` on this instance. + * + * @param transformer - to transform this instance + * @returns transformation of this instance + * @paramType T - type of the transformation result + */ + transform(transformer: AbstractTransformer): T { + return transformer.transformContainer(this); + } + + /** + * Dispatch the `transformer` on this instance in `context`. + * + * @param transformer - to transform this instance + * @param context - to be passed along to the `transformer` + * @returns transformation of this instance + * @paramType T - type of the transformation result + * @paramType ContextT - type of the transformation context + */ + transformWithContext( + transformer: AbstractTransformerWithContext, + context: ContextT + ): T { + return transformer.transformContainerWithContext( + this, context + ); + } + + constructor( + items: Array, + someItems: Array, + specialItems: Array | null = null + ) { + super(); + this.items = items; + this.someItems = someItems; + this.specialItems = specialItems; + } +} + +/** + * Iterate over the instances referenced from `that`, and recursively + * over their descendants if `recurse` is set. + */ +function *descend_Container( + that: Container, + recurse: boolean +): IterableIterator { + yield * descend_ListOf_IItem(that.items, recurse); + + yield * descend_ListOf_SomeItem(that.someItems, recurse); + + if (that.specialItems !== null) { + yield * descend_ListOf_SpecialItem(that.specialItems, recurse); + } +} + +export class Wrapper extends Class { + /** + * Indicate the runtime model type of the instance. + */ + modelType(): ModelType { + // NOTE (mristin): + // We yield numbers instead of literals to avoid name lookups on platforms + // which do not provide JIT compilation of hot paths. + return 3; // Wrapper + } + + container: Container; + + /** + * Iterate over the instances referenced from this instance. + * + * We do not recurse into the referenced instances. + * + * @returns Iterator over the referenced instances + */ + *descendOnce(): IterableIterator { + yield * descend_Wrapper(this, false); + } + + /** + * Iterate recursively over the instances referenced from this instance. + * + * @returns Iterator over the referenced instances + */ + *descend(): IterableIterator { + yield * descend_Wrapper(this, true); + } + + /** + * Dispatch `visitor` on this instance. + * + * @param visitor - to visit this instance + */ + accept(visitor: AbstractVisitor): void { + visitor.visitWrapper(this); + } + + /** + * Dispatch `visitor` with `context` on this instance. + * + * @param visitor - to visit this instance + * @param context - to be passed along to the dispatched visitor method + * @typeParam ContextT - type of the context + */ + acceptWithContext( + visitor: AbstractVisitorWithContext, + context: ContextT + ) { + visitor.visitWrapperWithContext(this, context); + } + + /** + * Dispatch the `transformer` on this instance. + * + * @param transformer - to transform this instance + * @returns transformation of this instance + * @paramType T - type of the transformation result + */ + transform(transformer: AbstractTransformer): T { + return transformer.transformWrapper(this); + } + + /** + * Dispatch the `transformer` on this instance in `context`. + * + * @param transformer - to transform this instance + * @param context - to be passed along to the `transformer` + * @returns transformation of this instance + * @paramType T - type of the transformation result + * @paramType ContextT - type of the transformation context + */ + transformWithContext( + transformer: AbstractTransformerWithContext, + context: ContextT + ): T { + return transformer.transformWrapperWithContext( + this, context + ); + } + + constructor(container: Container) { + super(); + this.container = container; + } +} + +/** + * Iterate over the instances referenced from `that`, and recursively + * over their descendants if `recurse` is set. + */ +function *descend_Wrapper( + that: Wrapper, + recurse: boolean +): IterableIterator { + yield that.container; + + if (recurse) { + yield * that.container.descend(); + } +} + +/** + * Iterate over the class instances held by `that`. + * + * If `recurse` is set, descend recursively into the instances as well. + */ +function *descend_ListOf_IItem( + that: Array, + recurse: boolean +): IterableIterator { + if (!recurse) { + yield * that; + return; + } + + for (const item of that) { + yield item; + + yield * item.descend(); + } +} + +/** + * Iterate over the class instances held by `that`. + * + * If `recurse` is set, descend recursively into the instances as well. + */ +function *descend_ListOf_SomeItem( + that: Array, + recurse: boolean +): IterableIterator { + if (!recurse) { + yield * that; + return; + } + + for (const item of that) { + yield item; + + yield * item.descend(); + } +} + +/** + * Iterate over the class instances held by `that`. + * + * If `recurse` is set, descend recursively into the instances as well. + */ +function *descend_ListOf_SpecialItem( + that: Array, + recurse: boolean +): IterableIterator { + if (!recurse) { + yield * that; + return; + } + + for (const item of that) { + yield item; + + yield * item.descend(); + } +} + +/** + * Visit the instances of the model. + */ +export abstract class AbstractVisitor { + /** + * Double-dispatch on `that`. + */ + visit(that: Class): void { + that.accept(this); + } + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + abstract visitSomeItem( + that: SomeItem + ): void; + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + abstract visitSpecialItem( + that: SpecialItem + ): void; + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + abstract visitContainer( + that: Container + ): void; + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + abstract visitWrapper( + that: Wrapper + ): void; +} + +/** + * Visit the instances of the model with context. + * + * @typeParam ContextT - type of the visitation context + */ +export abstract class AbstractVisitorWithContext { + /** + * Double-dispatch on `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + visitWithContext( + that: Class, + context: ContextT + ): void { + that.acceptWithContext(this, context); + } + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + abstract visitSomeItemWithContext( + that: SomeItem, + context: ContextT + ): void; + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + abstract visitSpecialItemWithContext( + that: SpecialItem, + context: ContextT + ): void; + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + abstract visitContainerWithContext( + that: Container, + context: ContextT + ): void; + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + abstract visitWrapperWithContext( + that: Wrapper, + context: ContextT + ): void; +} + +/** + * Visit the instances of the model without action. + * + * @remarks + * This visitor is not meant to be directly used. Instead, you usually + * inherit from it, and implement only the relevant visit methods. + */ +export class PassThroughVisitor extends AbstractVisitor { + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + visitSomeItem( + that: SomeItem + ): void { + for (const another of that.descendOnce()) { + this.visit(another); + } + } + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + visitSpecialItem( + that: SpecialItem + ): void { + for (const another of that.descendOnce()) { + this.visit(another); + } + } + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + visitContainer( + that: Container + ): void { + for (const another of that.descendOnce()) { + this.visit(another); + } + } + + /** + * Visit `that`. + * + * @param that - instance to be visited + */ + visitWrapper( + that: Wrapper + ): void { + for (const another of that.descendOnce()) { + this.visit(another); + } + } +} + +/** + * Visit the instances of the model without action and in context. + * + * @remarks + * This visitor is not meant to be directly used. Instead, you usually + * inherit from it, and implement only the relevant visit methods. + */ +export class PassThroughVisitorWithContext + extends AbstractVisitorWithContext { + /** + * Double-dispatch on `that` in `context`. + */ + visitWithContext( + that: Class, + context: ContextT + ): void { + that.acceptWithContext(this, context); + } + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + visitSomeItemWithContext( + that: SomeItem, + context: ContextT + ): void { + for (const another of that.descendOnce()) { + this.visitWithContext(another, context); + } + } + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + visitSpecialItemWithContext( + that: SpecialItem, + context: ContextT + ): void { + for (const another of that.descendOnce()) { + this.visitWithContext(another, context); + } + } + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + visitContainerWithContext( + that: Container, + context: ContextT + ): void { + for (const another of that.descendOnce()) { + this.visitWithContext(another, context); + } + } + + /** + * Visit `that` in `context`. + * + * @param that - instance to be visited + * @param context - of the visitation + */ + visitWrapperWithContext( + that: Wrapper, + context: ContextT + ): void { + for (const another of that.descendOnce()) { + this.visitWithContext(another, context); + } + } +} + +/** + * Transform the instance of the model. + * + * @typeParam T - type of the transformation result + */ +export abstract class AbstractTransformer { + /** + * Double-dispatch on `that`. + */ + transform(that: Class): T { + return that.transform(this); + } + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + abstract transformSomeItem( + that: SomeItem + ): T; + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + abstract transformSpecialItem( + that: SpecialItem + ): T; + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + abstract transformContainer( + that: Container + ): T; + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + abstract transformWrapper( + that: Wrapper + ): T; +} + +/** + * Transform the instances of the model in context. + * + * @typeParam ContextT - type of the transformation context + * @typeParam T - type of the transformation result + */ +export abstract class AbstractTransformerWithContext { + /** + * Double-dispatch on `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the transformation + * @returns transformed `that` + */ + transformWithContext( + that: Class, + context: ContextT + ): T { + return that.transformWithContext(this, context); + } + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the transformation + * @returns transformed `that` + */ + abstract transformSomeItemWithContext( + that: SomeItem, + context: ContextT + ): T; + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the transformation + * @returns transformed `that` + */ + abstract transformSpecialItemWithContext( + that: SpecialItem, + context: ContextT + ): T; + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the transformation + * @returns transformed `that` + */ + abstract transformContainerWithContext( + that: Container, + context: ContextT + ): T; + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the transformation + * @returns transformed `that` + */ + abstract transformWrapperWithContext( + that: Wrapper, + context: ContextT + ): T; +} + +/** + * Transform the instances of the model. + * + * @remarks + * If you do not override the transformation methods, they simply + * return {@link defaultResult}. + * + * @typeParam T - type of the transformation result + */ +export class TransformerWithDefault extends AbstractTransformer { + /** + * Default value which is returned if no override of the transformation + */ + defaultResult: T + + /** + * Initialize with the given `default` value. + * + * @param defaultResult - returned if no override of the transformation + */ + constructor(defaultResult: T) { + super(); + this.defaultResult = defaultResult; + } + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSomeItem( + that: SomeItem + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSpecialItem( + that: SpecialItem + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformContainer( + that: Container + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that`. + * + * @param that - instance to be transformed + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformWrapper( + that: Wrapper + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ +} + +/** + * Transform the instances of the model in context. + * + * @remarks + * If you do not override the transformation methods, they simply + * return {@link defaultResult}. + * + * @typeParam ContextT - type of the visitation context + * @typeParam T - type of the transformation result + */ +export class TransformerWithDefaultAndContext + extends AbstractTransformerWithContext { + /** + * Default value which is returned if no override of the transformation + */ + defaultResult: T + + /** + * Initialize with the given `default` value. + * + * @param defaultResult - returned if no override of the transformation + */ + constructor(defaultResult: T) { + super(); + this.defaultResult = defaultResult; + } + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the visitation + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSomeItemWithContext( + that: SomeItem, + context: ContextT + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the visitation + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSpecialItemWithContext( + that: SpecialItem, + context: ContextT + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the visitation + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformContainerWithContext( + that: Container, + context: ContextT + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /** + * Transform `that` in `context`. + * + * @param that - instance to be transformed + * @param context - of the visitation + * @returns transformed `that` + */ + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformWrapperWithContext( + that: Wrapper, + context: ContextT + ): T { + return this.defaultResult; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ +} + +/** + * Try to cast an instance of the model to {@link IItem}. + */ +class AsItemTransformer + extends AbstractTransformer { + transformSomeItem( + that: SomeItem + ): IItem | null { + return that as IItem; + } + + transformSpecialItem( + that: SpecialItem + ): IItem | null { + return that as IItem; + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformContainer( + that: Container + ): IItem | null { + return null; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformWrapper( + that: Wrapper + ): IItem | null { + return null; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ +} + +const AS_ITEM_TRANSFORMER = + new AsItemTransformer(); + +/** + * Try to cast `that` instance to + * the interface {@link IItem}. + * + * @param that - instance to be casted + * @returns - casted `that` if cast successful, or `null` + */ +export function asItem( + that: Class +): IItem | null { + return AS_ITEM_TRANSFORMER.transform(that); +} + +/** + * Check the type of `that` instance. + * + * @param that - instance to be type-checked + * @returns `true` if the type check is successful + */ +export function isItem( + that: Class +): that is IItem { + return asItem(that) !== null; +} + +/** + * Try to cast an instance of the model to {@link ISomeItem}. + */ +class AsSomeItemTransformer + extends AbstractTransformer { + transformSomeItem( + that: SomeItem + ): ISomeItem | null { + return that as ISomeItem; + } + + transformSpecialItem( + that: SpecialItem + ): ISomeItem | null { + return that as ISomeItem; + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformContainer( + that: Container + ): ISomeItem | null { + return null; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformWrapper( + that: Wrapper + ): ISomeItem | null { + return null; + } + /* eslint-enable @typescript-eslint/no-unused-vars */ +} + +const AS_SOME_ITEM_TRANSFORMER = + new AsSomeItemTransformer(); + +/** + * Try to cast `that` instance to + * the interface {@link ISomeItem}. + * + * @param that - instance to be casted + * @returns - casted `that` if cast successful, or `null` + */ +export function asSomeItem( + that: Class +): ISomeItem | null { + return AS_SOME_ITEM_TRANSFORMER.transform(that); +} + +/** + * Check the type of `that` instance. + * + * @param that - instance to be type-checked + * @returns `true` if the type check is successful + */ +export function isSomeItem( + that: Class +): that is ISomeItem { + return asSomeItem(that) !== null; +} + +/** + * Try to cast `that` instance to + * the class {@link SpecialItem}. + * + * @param that - instance to be casted + * @returns - casted `that` if cast successful, or `null` + */ +export function asSpecialItem( + that: Class +): SpecialItem | null { + return (that instanceof SpecialItem) + ? that + : null; +} + +/** + * Check the type of `that` instance. + * + * @param that - instance to be type-checked + * @returns `true` if the type check is successful + */ +export function isSpecialItem( + that: Class +): that is SpecialItem { + return that instanceof SpecialItem; +} + +/** + * Try to cast `that` instance to + * the class {@link Container}. + * + * @param that - instance to be casted + * @returns - casted `that` if cast successful, or `null` + */ +export function asContainer( + that: Class +): Container | null { + return (that instanceof Container) + ? that + : null; +} + +/** + * Check the type of `that` instance. + * + * @param that - instance to be type-checked + * @returns `true` if the type check is successful + */ +export function isContainer( + that: Class +): that is Container { + return that instanceof Container; +} + +/** + * Try to cast `that` instance to + * the class {@link Wrapper}. + * + * @param that - instance to be casted + * @returns - casted `that` if cast successful, or `null` + */ +export function asWrapper( + that: Class +): Wrapper | null { + return (that instanceof Wrapper) + ? that + : null; +} + +/** + * Check the type of `that` instance. + * + * @param that - instance to be type-checked + * @returns `true` if the type check is successful + */ +export function isWrapper( + that: Class +): that is Wrapper { + return that instanceof Wrapper; +} + +class TypeMatcher extends AbstractTransformerWithContext< + Readonly, + boolean +> { + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSomeItemWithContext( + that: SomeItem, + other: Class + ): boolean { + return isSomeItem(other); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformSpecialItemWithContext( + that: SpecialItem, + other: Class + ): boolean { + return isSpecialItem(other); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformContainerWithContext( + that: Container, + other: Class + ): boolean { + return isContainer(other); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + + /* eslint-disable @typescript-eslint/no-unused-vars */ + transformWrapperWithContext( + that: Wrapper, + other: Class + ): boolean { + return isWrapper(other); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ +} + +const TYPE_MATCHER = new TypeMatcher(); + +/** + * Check whether the type of `that` matches the type of `other` instance. + * + * @remarks + * We check with `is*` function. Hence, if the class of `other` is a subclass of + * the class of `that`, we confirm the match. + * + * @param that - standard instance + * @param other - instance whose type is compared against `that` + */ +export function typesMatch( + that: ClassT, + other: Class +): other is ClassT { + return TYPE_MATCHER.transformWithContext(that, other); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/verification.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/verification.ts new file mode 100644 index 000000000..307ac6527 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/verification.ts @@ -0,0 +1,818 @@ +/** + * Verify that the instances of the meta-model satisfy the invariants. + * + * Here is an example how to verify an instance of {@link types.SomeItem}: + * + * ```ts + * import * as OurTypes from "@dummy-works/dummy/types"; + * import * as OurVerification from "@dummy-works/dummy/verification"; + * + * const anInstance = new OurTypes.SomeItem( + * // ... some constructor arguments ... + * ); + * + * for (const error of OurVerification.verify(anInstance)) { + * console.log(`${error.message} at: ${error.path}`); + * } + * ``` + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurCommon from "./common"; +import * as OurConstants from "./constants"; +import * as OurTypes from "./types"; + +// The generated code might contain deliberately double negations. For example, +// when the constraint is formulated as a NAND and we check that the constraint +// is not fulfilled. Therefore, we disable this linting rule. +/* eslint no-extra-boolean-cast: 0 */ + +// NOTE (mristin): +// The SDK defines three of these path vocabularies: this one, the one in +// the `jsonization` module, and the one in the `xmlcommon` module. They look +// alike, and it is tempting to merge them, but they are not interchangeable. +// +// Each of them points into a different thing: +// +// * This one points into the instances which you built, so a property segment +// holds a class of the meta-model. +// * The jsonization's points into the JSON-able structure being read or +// written, so a property segment holds the JSON-able object instead: while +// a document is being parsed, the instance which the property would belong +// to does not exist yet. +// * The xmlcommon's points into the XML document, where there are no +// properties at all, only elements, so it names an element instead. It is +// also the only one whose segments carry no back-pointer, as the reading is +// a single pass over a token stream and the element is gone by the time +// the error comes back out. +// +// The three also render differently: a path into the instances is +// a TypeScript access expression, a path into a JSON-able structure starts at +// the root of the document and carries no leading dot, and a path into an XML +// document is a relative XPath. +// +// Merging them would mean either dropping the back-pointers, which have been +// part of the public API of this SDK since before these modules were split +// apart, or defining a single path over the union of all the segment kinds -- +// in which case every consumer would have to handle segments which can never +// occur in its world. Three small vocabularies which each say exactly what +// they can say cost less than one large one which lies about its range. + +/** + * Represent a property access on a path to an erroneous value. + */ +export class PropertySegment { + /** + * Instance containing the property + */ + readonly instance: OurTypes.Class; + + /** + * Name of the property + */ + readonly name: string; + + constructor(instance: OurTypes.Class, name: string) { + this.instance = instance; + this.name = name; + } + + toString(): string { + return `.${this.name}`; + } +} + +/** + * Represent an index access on a path to an erroneous value. + */ +export class IndexSegment { + /** + * Sequence containing the item at {@link index} + */ + readonly sequence: ReadonlyArray; + + /** + * Index of the item in the {@link sequence} + */ + readonly index: number; + + constructor(sequence: ReadonlyArray, index: number) { + this.sequence = sequence; + this.index = index; + } + + toString(): string { + return `[${this.index}]`; + } +} + +/** + * Represent a member access on a path to an erroneous value. + * + * @remarks + * + * Unlike a {@link PropertySegment}, which names a property of one of our + * classes, a key names a member of an open JSON-able object. It is known only + * at run time, and can be any string at all, so it is always rendered as + * a subscript. + */ +export class KeySegment { + /** + * Object containing the value at {@link key} + */ + readonly object: { readonly [key: string]: unknown }; + + /** + * Key of the value in the {@link object} + */ + readonly key: string; + + constructor(object: { readonly [key: string]: unknown }, key: string) { + this.object = object; + this.key = key; + } + + toString(): string { + return `[${JSON.stringify(this.key)}]`; + } +} + +export type Segment = PropertySegment | IndexSegment | KeySegment; + +/** + * Represent the relative path to the erroneous value. + */ +export class Path { + readonly segments: Array = []; + + prepend(segment: Segment): void { + this.segments.unshift(segment); + } + + toString(): string { + return this.segments.join(""); + } +} + +/** + * Represent a verification error in the data. + */ +export class VerificationError { + // NOTE (mristin): + // The name `VerificationError` is redundant since it lives in `verification` module, + // and it would have made more sense to call it simply `Error`. Unfortunately in this case, + // `Error` is a reserved name by JavaScript. + + /** + * Human-readable description of the error + */ + readonly message: string; + + /** + * Path to the erroneous value + */ + readonly path: Path = new Path(); + + /** + * Initialize with the given `message` and `path`. + * + * @remarks + * If no `path` is specified, initialize with an empty path. + */ + constructor(message: string, path: Path | null = null) { + this.message = message; + this.path = (path !== null) + ? path + : new Path(); + } +} + +/** + * Check that the names of the `items` are unique. + * + * @remarks + * + * The `items` are a generic sequence, so the sequences of + * the descendants of {@link types!IItem} can be passed without a copy. + */ +export function namesAreUnique( + items: Array +): boolean { + const nameSet: Set = new Set(); + for (const item of items) { + if (nameSet.has(item.name)) { + return false; + } + nameSet.add(item.name); + } + return true; +} + +/** + * Iterate over a generic sequence in a generator. + */ +export function namesAreNonEmpty( + items: Array +): boolean { + return OurCommon.every( + OurCommon.map( + items, + item => + OurCommon.lenStr(item.name) > 0 + ) + ); +} + +/** + * Narrow the items of a generic sequence in a generator. + */ +export function someSpecialItemHasLevel( + items: Array +): boolean { + return OurCommon.some( + OurCommon.map( + items, + item => + ( + OurTypes.isSpecialItem(item) + && (item as OurTypes.SpecialItem).level > 0 + ) + ) + ); +} + +/** + * Index a generic sequence, and narrow the item. + */ +export function firstIsSpecial( + items: Array +): boolean { + if (items.length == 0) { + return false; + } + const first = OurCommon.at(items, 0); + if (OurTypes.isSpecialItem(first)) { + return (first as OurTypes.SpecialItem).level > 0; + } + return false; +} + +/** + * Check that the nicknames of the `items` are unique, if specified. + * + * @remarks + * + * This mirrors the body of `ID_shorts_are_unique` in aas-core-meta V3.2: + * a local variable takes an optional property of an item of a generic sequence, + * and the loop continues if it is None. + */ +export function nicknamesAreUnique( + items: Array +): boolean { + const nicknameSet: Set = new Set(); + for (const item of items) { + const nickname = item.nickname; + if (nickname === null) { + continue; + } + if (nicknameSet.has(nickname)) { + return false; + } + nicknameSet.add(nickname); + } + return true; +} + +/** + * Narrow the loop variable over a generic sequence in a for-loop. + * + * @remarks + * + * The loop variable is of the type parameter in Go, so the down-cast needs to + * go through `any(...)`, and of the template parameter in C++. + */ +export function levelsOfSpecialItemsArePositive( + items: Array +): boolean { + for (const item of items) { + if (OurTypes.isSpecialItem(item)) { + if ((item as OurTypes.SpecialItem).level <= 0) { + return false; + } + } + } + return true; +} + +/** + * Narrow an immutable local variable which holds an item of a generic sequence. + * + * @remarks + * + * In contrast to {@link firstIsSpecial}, the local variable is annotated, so + * it is of the parent class instead of the type parameter. Hence, Go down-casts + * it without `any(...)`, and C++ binds the item to a constant reference to + * the shared pointer of the parent class. + */ +export function lastIsSpecial( + items: Array +): boolean { + if (items.length == 0) { + return false; + } + const last: OurTypes.IItem = OurCommon.at( + items, + items.length - 1 + ); + if (OurTypes.isSpecialItem(last)) { + return (last as OurTypes.SpecialItem).level > 0; + } + return false; +} + +/** + * Mix two generic sequences with an argument which is not generic. + * + * @remarks + * + * The names must differ, except for the `sharedName`, which may repeat. + * + * Go declares two type parameters, and C++ two template parameters. The nested + * generators iterate over two different generic sequences, so their variables + * are of two different type parameters. + */ +export function namesAreDisjoint( + items: Array, + otherItems: Array, + sharedName: string +): boolean { + return OurCommon.every( + OurCommon.map( + items, + item => + OurCommon.every( + OurCommon.map( + otherItems, + otherItem => + ( + item.name != otherItem.name + || item.name == sharedName + ) + ) + ) + ) + ); +} + +/** + * Use the immutable aliases of the generic sequences. + * + * @remarks + * + * An immutable alias of the very same type shares the type annotation of + * the aliased generic sequence, so it is a generic sequence itself. Go declares + * the alias with the type parameter, C++ with the template parameter, while C# + * and Java infer its type with `var`. The `alias_of_alias` aliases an alias, + * and the `optional_alias` aliases an optional generic sequence. + */ +export function aliasedNamesAreUnique( + items: Array, + optionalItems: Array | null +): boolean { + const alias: Array = items; + const aliasOfAlias: Array = alias; + const optionalAlias: Array | null = optionalItems; + for (const item of alias) { + if (OurTypes.isSpecialItem(item)) { + if ((item as OurTypes.SpecialItem).level <= 0) { + return false; + } + } + } + return ( + ( + OurCommon.every( + OurCommon.map( + aliasOfAlias, + item => + OurCommon.lenStr(item.name) > 0 + ) + ) + ) + && namesAreUnique(aliasOfAlias) + && optionalNamesAreUnique(optionalAlias) + && ( + ( + (optionalAlias === null) + || namesAreUnique(optionalAlias) + ) + ) + ); +} + +/** + * Look up an instance of a descendant in a generic sequence. + * + * @remarks + * + * Go compares the instances as `any`'s, as the items are of the type parameter, + * and C++ converts the member to the shared pointer of {@link types!IItem} so that + * the raw pointers are comparable for any template parameter. + */ +export function containsSomeItem( + someItem: OurTypes.SomeItem, + items: Array +): boolean { + return items.includes(someItem); +} + +/** + * Look up the items of a generic sequence in another generic sequence. + * + * @remarks + * + * The member and the items are of two different type parameters. + */ +export function itemsOverlap( + items: Array, + otherItems: Array +): boolean { + return OurCommon.some( + OurCommon.map( + otherItems, + otherItem => + items.includes(otherItem) + ) + ); +} + +/** + * Look up an instance in a sequence which is not generic. + * + * @remarks + * + * The {@link types!SpecialItem} has no descendants, so the `items` are not + * a generic sequence, but Go still compares the instances as `any`'s. + */ +export function containsSpecialItem( + specialItem: OurTypes.SpecialItem, + items: Array +): boolean { + return items.includes(specialItem); +} + +/** + * Pass the local variables on to generic sequences. + * + * @remarks + * + * The `some_items` is a read-only alias without an annotation, while + * the `all_some_items` is an immutable local variable declared with + * `Final[...]`. Both hold the sequences of {@link types!ISomeItem}, and are passed + * on to the generic sequences of {@link types!IItem}, required as well as optional. + */ +export function containerNamesAreUnique( + container: OurTypes.Container +): boolean { + const someItems = container.someItems; + const allSomeItems: Array = container.someItems; + return ( + namesAreUnique(someItems) + && namesAreUnique(allSomeItems) + && optionalNamesAreUnique(allSomeItems) + ); +} + +/** + * Pass an optional generic sequence on to a generic sequence. + */ +export function optionalNamesAreUnique( + items: Array | null +): boolean { + return ( + (items === null) + || namesAreUnique(items) + ); +} + +/** + * Pass the generic sequences of the descendants on. + * + * @remarks + * + * The `someItems` are passed on as a required and as an optional + * sequence of the parent class, while the `specialItems` are passed on + * as an optional sequence and, once narrowed, as a required sequence of the parent + * class of the parent class. + */ +export function someNamesAreUnique( + someItems: Array, + specialItems: Array | null +): boolean { + return ( + namesAreUnique(someItems) + && optionalNamesAreUnique(someItems) + && optionalNamesAreUnique(specialItems) + && optionalNamesAreUnique(null) + && ( + ( + (specialItems === null) + || namesAreUnique(specialItems) + ) + ) + ); +} + +/** + * Check that the levels are positive. + * + * @remarks + * + * The {@link types!SpecialItem} has no descendants, so the `items` are not + * a generic sequence. + */ +export function levelsArePositive( + items: Array +): boolean { + return OurCommon.every( + OurCommon.map( + items, + item => + item.level > 0 + ) + ); +} + +/** + * Verify the items of `that` recursively. + */ +function *verify_ListOf_class( + that: ReadonlyArray +): IterableIterator { + for (let i = 0; i < that.length; i++) { + for (const error of verify(that[i])) { + error.path.prepend( + new IndexSegment( + that, + i + ) + ); + yield error; + } + } +} + +/** + * Verify an instance of the model recursively or non-recursively (depending on the context). + */ +class Verifier + extends OurTypes.AbstractTransformerWithContext< + boolean, IterableIterator + > { + *transformSomeItemWithContext( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + that: OurTypes.SomeItem, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + context: boolean + ): IterableIterator { + // No verification has been defined for SomeItem. + } + + *transformSpecialItemWithContext( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + that: OurTypes.SpecialItem, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + context: boolean + ): IterableIterator { + // No verification has been defined for SpecialItem. + } + + *transformContainerWithContext( + that: OurTypes.Container, + context: boolean + ): IterableIterator { + if (!( + !(that.specialItems !== null) + || ( + OurCommon.every( + OurCommon.map( + that.specialItems, + specialItem => + containsSpecialItem(specialItem, that.specialItems) + ) + ) + ) + )) { + yield new VerificationError( + "Each special item must be among the special items." + ) + } + + if (!( + ( + that.someItems.length == 0 + || itemsOverlap(that.items, that.someItems) + ) + )) { + yield new VerificationError( + "If there are some items, they must overlap with the items." + ) + } + + if (!( + OurCommon.every( + OurCommon.map( + that.someItems, + someItem => + containsSomeItem(someItem, that.items) + ) + ) + )) { + yield new VerificationError( + "Some items must be among the items." + ) + } + + if (!( + aliasedNamesAreUnique( + that.someItems, + that.specialItems + ) + )) { + yield new VerificationError( + "Names of some items and of special items must be unique " + + "through aliases." + ) + } + + if (!namesAreDisjoint(that.items, that.someItems, "x")) { + yield new VerificationError( + "Names of the items and of some items must be disjoint." + ) + } + + if (!( + !someSpecialItemHasLevel(that.items) + || lastIsSpecial(that.items) + )) { + yield new VerificationError( + "If there is a special item with level, the last item must " + + "be special." + ) + } + + if (!levelsOfSpecialItemsArePositive(that.items)) { + yield new VerificationError( + "Levels of the special items among the items must be " + + "positive." + ) + } + + if (!nicknamesAreUnique(that.someItems)) { + yield new VerificationError( + "Nicknames of some items must be unique." + ) + } + + if (!(that.countItems(that.items) == that.items.length)) { + yield new VerificationError( + "Count of the items must match the number of the items." + ) + } + + if (!optionalNamesAreUnique(that.specialItems)) { + yield new VerificationError( + "Names of the special items must be unique." + ) + } + + if (!( + !(that.specialItems !== null) + || firstIsSpecial(that.specialItems) + )) { + yield new VerificationError( + "The first special item must be special." + ) + } + + if (!( + !(that.specialItems !== null) + || levelsArePositive(that.specialItems) + )) { + yield new VerificationError( + "Levels of the special items must be positive." + ) + } + + if (!( + !someSpecialItemHasLevel(that.items) + || firstIsSpecial(that.items) + )) { + yield new VerificationError( + "If there is a special item with level, the first item must " + + "be special." + ) + } + + if (!namesAreNonEmpty(that.someItems)) { + yield new VerificationError( + "Names of some items must be non-empty." + ) + } + + if (!( + someNamesAreUnique( + that.someItems, + that.specialItems + ) + )) { + yield new VerificationError( + "Names of some items and of special items must be unique." + ) + } + + if (!namesAreUnique(that.items)) { + yield new VerificationError( + "Names of the items must be unique." + ) + } + + if (context === true) { + for (const error of verify_ListOf_class(that.items)) { + error.path.prepend( + new PropertySegment( + that, + "items" + ) + ); + yield error; + } + + for (const error of verify_ListOf_class(that.someItems)) { + error.path.prepend( + new PropertySegment( + that, + "someItems" + ) + ); + yield error; + } + + if (that.specialItems !== null) { + for (const error of verify_ListOf_class(that.specialItems)) { + error.path.prepend( + new PropertySegment( + that, + "specialItems" + ) + ); + yield error; + } + } + } + } + + *transformWrapperWithContext( + that: OurTypes.Wrapper, + context: boolean + ): IterableIterator { + if (!containerNamesAreUnique(that.container)) { + yield new VerificationError( + "Names of some items of the container must be unique." + ) + } + + if (context === true) { + for (const error of verify(that.container)) { + error.path.prepend( + new PropertySegment( + that, + "container" + ) + ); + yield error; + } + } + } +} + +const VERIFIER = new Verifier(); + +/** + * Verify the constraints of `that`. + * + * @param that - instance to be verified + * @param recurse - if set, continue the verification recursively + * @returns a stream of verification errors + */ +export function *verify( + that: OurTypes.Class, + recurse = true +): IterableIterator { + yield * VERIFIER.transformWithContext(that, recurse); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlcommon.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlcommon.ts new file mode 100644 index 000000000..d9c82f917 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlcommon.ts @@ -0,0 +1,1045 @@ +/** + * Provide the XML primitives shared by the modules of this SDK which read and + * write XML. + * + * @remarks + * + * The skipping of the ignorable tokens, the checks of the namespace and + * the consumption of the tags are spelled out here once, instead of once in + * the de/serialization of the model and once again wherever else a document is + * read or written. + * + * The XML namespace is a constant of this module, and not an argument which + * every primitive takes. Every parser wears one and the same shape, + * {@link ContentParser}, which is handed the cursor and nothing else, so that + * a parser can be given to another parser without allocating a closure; and + * one SDK speaks of exactly one namespace anyhow. + * + * The elements of the XML-RPC subset, over which a JSON-able value is + * de/serialized, are the one exception: they reside in no namespace at all, + * and are read with the primitives named `...InNoNamespace`. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurCommon from "./common"; + +import { + CdataToken, + CloseTagToken, + CommentToken, + EndToken, + OpenTagToken, + ProcessingInstructionToken, + TextToken, + XmlAnyToken, + XmlSaxParser +} from "xmlsax-typescript"; + +export const NAMESPACE = "https://dummy.com"; + +// NOTE (mristin): +// The SDK defines three of these path vocabularies: this one, the one in +// the `jsonization` module, and the one in the `xmlcommon` module. They look +// alike, and it is tempting to merge them, but they are not interchangeable. +// +// Each of them points into a different thing: +// +// * This one points into the instances which you built, so a property segment +// holds a class of the meta-model. +// * The jsonization's points into the JSON-able structure being read or +// written, so a property segment holds the JSON-able object instead: while +// a document is being parsed, the instance which the property would belong +// to does not exist yet. +// * The xmlcommon's points into the XML document, where there are no +// properties at all, only elements, so it names an element instead. It is +// also the only one whose segments carry no back-pointer, as the reading is +// a single pass over a token stream and the element is gone by the time +// the error comes back out. +// +// The three also render differently: a path into the instances is +// a TypeScript access expression, a path into a JSON-able structure starts at +// the root of the document and carries no leading dot, and a path into an XML +// document is a relative XPath. +// +// Merging them would mean either dropping the back-pointers, which have been +// part of the public API of this SDK since before these modules were split +// apart, or defining a single path over the union of all the segment kinds -- +// in which case every consumer would have to handle segments which can never +// occur in its world. Three small vocabularies which each say exactly what +// they can say cost less than one large one which lies about its range. + +/** + * Escape the characters which an XPath can not carry literally. + * + * @remarks + * + * Neither an ampersand nor a less-than sign can occur in a valid element name, + * but they are escaped all the same, so that a bug report over a broken + * document still reads. + */ +function escapeForXPath(text: string): string { + // NOTE (mristin): + // Mind the order -- the ampersand has to go first, or the ampersands of + // the replacements below would be escaped in their turn. + return text + .replace(/&/g, "&") + .replace(/\//g, "/") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +/** + * Represent an element on a path to the erroneous value. + */ +export class ElementSegment { + /** + * Local name of the element, without the namespace + */ + readonly name: string; + + constructor(name: string) { + this.name = name; + } + + /** + * Render the segment as a tag without the namespace. + * + * @remarks + * + * We deliberately omit the namespace in the tag names. If you want to + * actually query with the resulting XPath, you have to insert the namespaces + * manually. We did not know how to include the namespace in a meaningful way, + * as XPath assumes namespace prefixes to be defined *outside* of the document. + * At least the path thus rendered is informative, and you should be able to + * descend it manually. + */ + toString(): string { + return escapeForXPath(this.name); + } +} + +/** + * Represent an element in a sequence on a path to the erroneous value. + */ +export class IndexSegment { + /** + * Index of the element in the sequence + */ + readonly index: number; + + constructor(index: number) { + this.index = index; + } + + /** + * Render the segment as an element wildcard with the 1-based index of XPath. + */ + toString(): string { + return `*[${this.index + 1}]`; + } +} + +/** + * Represent a member of an open JSON-able object on a path to the erroneous + * element. + * + * @remarks + * + * Unlike an {@link ElementSegment}, which names an element prescribed by + * the meta-model, a key is known only at run time, and can be any string at + * all. + */ +export class KeySegment { + /** + * Key of the member + */ + readonly key: string; + + constructor(key: string) { + this.key = key; + } + + /** + * Render the segment as a predicate on the `` child element. + * + * @remarks + * + * A JSON-able object is written as an XML-RPC ``, which carries + * the key of a member in a `` child element instead of in an attribute, + * so the XPath has to match on that child element. + */ + toString(): string { + return `member[name="${escapeForXPath(this.key)}"]`; + } +} + +export type Segment = ElementSegment | IndexSegment | KeySegment; + +/** + * Represent the relative path to the erroneous element. + */ +export class Path { + private readonly _segments = new Array(); + + segments(): Array { + return this._segments; + } + + prepend(segment: Segment): void { + this._segments.unshift(segment); + } + + /** + * Render the path as a relative XPath. + * + * @remarks + * + * We omit the leading `/` so that you can easily prefix it as you need. + */ + toString(): string { + return this._segments.join("/"); + } +} + +/** + * Signal that XML de-serialization could not be performed. + */ +export class DeserializationError { + readonly message: string; + readonly path: Path; + + constructor(message: string, path: Path | null = null) { + this.message = message; + this.path = path ?? new Path(); + } +} + +/** + * Signal that XML serialization could not be performed. + * + * @remarks + * + * The {@link SerializationError.path} points into the instance which was handed + * over for the serialization, and *not* into an XML document -- at the point of + * the failure there is no document yet. For example, `.submodels[0].id` tells you + * that the serialization broke on `that.submodels[0].id`. + * + * The path is therefore a TypeScript access expression, and not the relative + * XPath which the de-serialization reports, and it is a plain string, unlike + * the structured {@link Path} of the de-serialization: the elements which + * the XML representation adds on top of the instance contribute no segment, as + * they correspond to no property access. This concerns the element enclosing + * the instance itself, and the element which designates the model type of + * the value of a property -- a caller holding the instance reaches the value as, + * say, `value.idShort` and not as `value/idShort`. + */ +export class SerializationError extends Error { + private readonly _segments = new Array(); + + /** + * Render the path to the erroneous value as a TypeScript access expression. + */ + get path(): string { + return this._segments.join(""); + } + + /** + * Insert the access to the property `name` before the {@link path}. + */ + prependProperty(name: string): void { + this._segments.unshift(`.${name}`); + } + + /** + * Insert the access to the item at `index` before the {@link path}. + */ + prependIndex(index: number): void { + this._segments.unshift(`[${index}]`); + } + + /** + * Insert the access to the member `key` before the {@link path}. + * + * @remarks + * + * Unlike a property of one of our classes, a member of an open JSON-able + * object is known only at run time and can be any string at all, so it is + * always rendered as a subscript. + */ + prependKey(key: string): void { + this._segments.unshift(`[${JSON.stringify(key)}]`); + } +} + +export function newDeserializationError( + message: string +): OurCommon.Either { + return new OurCommon.Either( + null, + new DeserializationError(message) + ); +} + +/** + * Parse a value from `cursor`. + * + * Every parser in this module wears this one shape, which is what lets them + * compose: a parser can be given to another parser without a closure, since + * everything it needs comes from the `cursor` and from its own definition. + * The name says what a parser consumes -- a `parse*Content` stops right before + * the closing tag of the element which the caller has opened, while + * a `parse*Element` and a `dispatchParse*Element` read an element of their own, + * its tags included. + * + * @typeParam T - type of the parsed value + */ +export type ContentParser = ( + cursor: XmlCursor +) => OurCommon.Either; + +export function currentTokenKind(cursor: XmlCursor): string { + const token = cursor.current(); + if (token === null) { + return "end-of-token-stream"; + } + + return token.kind; +} + +export function localNameOfTag(tag: unknown): string { + const aTag = tag as { + name?: unknown, + local?: unknown, + localName?: unknown + }; + + if (typeof aTag.local === "string") { + return aTag.local; + } + if (typeof aTag.localName === "string") { + return aTag.localName; + } + if (typeof aTag.name === "string") { + const colonIndex = aTag.name.indexOf(":"); + if (colonIndex >= 0) { + return aTag.name.substring(colonIndex + 1); + } + return aTag.name; + } + + return ""; +} + +function namespaceOfTag(tag: unknown): string { + const aTag = tag as { uri?: unknown, namespaceURI?: unknown }; + if (typeof aTag.uri === "string") { + return aTag.uri; + } + if (typeof aTag.namespaceURI === "string") { + return aTag.namespaceURI; + } + return ""; +} + +function describeNamespace(namespace: string): string { + return namespace.length === 0 ? "no namespace" : `'${namespace}'`; +} + +function checkExpectedOpenTagNamespace( + openTag: OpenTagToken, + expectedNamespace: string +): DeserializationError | null { + const namespace = namespaceOfTag(openTag.tag); + if (namespace !== expectedNamespace) { + return new DeserializationError( + `Expected XML namespace ${describeNamespace(expectedNamespace)}, ` + + `but got ${describeNamespace(namespace)}` + ); + } + + return null; +} + +/** + * Read the next property's opening tag while parsing the sequence of + * properties of `className`, advancing `cursor` past it. + * + * @param cursor - to be read from + * @param className - name of the class being parsed, for error reporting + * @returns + * the next property's opening tag, or `null` if the closing tag of + * `className` was reached instead, or an error + */ +export function nextPropertyOpenTag( + cursor: XmlCursor, + className: string +): OpenTagToken | DeserializationError | null { + const token = cursor.current(); + if (token === null) { + return new DeserializationError( + `Unexpected end of token stream while parsing ${className}` + ); + } + + if (token instanceof CloseTagToken) { + return null; + } + + if (!(token instanceof OpenTagToken)) { + return new DeserializationError( + "Expected an XML property start element or the closing element of " + + `${className}, but got token kind: ${token.kind}` + ); + } + + const namespaceError = checkExpectedOpenTagNamespace(token, NAMESPACE); + if (namespaceError !== null) { + return namespaceError; + } + + cursor.advance(); + return token; +} + +function checkExpectedCloseTag( + closeTag: CloseTagToken, + expectedLocalName: string, + expectedNamespace: string +): DeserializationError | null { + const namespace = namespaceOfTag(closeTag.tag); + if (namespace !== expectedNamespace) { + return new DeserializationError( + `Expected XML namespace ${describeNamespace(expectedNamespace)}, ` + + `but got ${describeNamespace(namespace)}` + ); + } + + const observedLocalName = localNameOfTag(closeTag.tag); + if (observedLocalName !== expectedLocalName) { + return new DeserializationError( + `Expected closing XML element '${expectedLocalName}', ` + + `but got '${observedLocalName}'` + ); + } + + return null; +} + +/** + * Read the next token from `cursor`, expecting it to be the closing XML + * element named `expectedLocalName`, and consume it. + */ +export function consumeCloseTag( + cursor: XmlCursor, + expectedLocalName: string +): DeserializationError | null { + return consumeCloseTagInNamespace(cursor, expectedLocalName, NAMESPACE); +} + +function consumeCloseTagInNamespace( + cursor: XmlCursor, + expectedLocalName: string, + expectedNamespace: string +): DeserializationError | null { + const closeTag = cursor.current(); + if (!(closeTag instanceof CloseTagToken)) { + return new DeserializationError( + `Expected a closing element '${expectedLocalName}', ` + + `but got token kind: ${currentTokenKind(cursor)}` + ); + } + + const closeError = checkExpectedCloseTag( + closeTag, expectedLocalName, expectedNamespace + ); + if (closeError !== null) { + return closeError; + } + + cursor.advance(); + return null; +} + +/** + * Read the next non-ignorable token from `cursor`, expecting it to be + * an opening XML element in the expected namespace. + * + * This is shared by the parsing of a single list item, a single tuple + * item, and the dispatch-parsing of an interface. + * + * @param cursor - to read from + * @returns the opening tag, or an error + */ +export function readNextOpenTag( + cursor: XmlCursor +): OurCommon.Either { + return readNextOpenTagInNamespace(cursor, NAMESPACE); +} + +function readNextOpenTagInNamespace( + cursor: XmlCursor, + expectedNamespace: string +): OurCommon.Either { + cursor.skipIgnorable(); + const token = cursor.current(); + if (token === null) { + return newDeserializationError( + "Expected an XML element, but got end of token stream" + ); + } + if (!(token instanceof OpenTagToken)) { + return newDeserializationError( + `Expected an XML element, but got token kind: ${token.kind}` + ); + } + + const namespaceError = checkExpectedOpenTagNamespace( + token, expectedNamespace + ); + if (namespaceError !== null) { + return new OurCommon.Either( + null, + namespaceError + ); + } + + return new OurCommon.Either(token, null); +} + +/** + * Parse the content of the XML element which the caller has opened, and consume + * the corresponding closing element named `localName`. + * + * This is the one thing which every value of an XML element has in common, + * whatever it holds: the property loop of a class calls it directly, since + * it has already read the opening tag and switched on its local name, and + * `parseNamedElement` calls it after reading an opening tag of its own. + * + * A property loop assigns *both* halves of the result -- the value as well as + * the error -- without looking at either first. The loop returns as soon as + * the error is set, so the `null` value which comes with an error is never + * read, and the property's variable needs no guard. + * + * @param cursor - to read from + * @param localName - local name of the element which the caller has opened + * @param parseContent - parses the content of the element + * @returns parsed value, or an error + * @typeParam T - type of the parsed value + */ +export function parseElementContent( + cursor: XmlCursor, + localName: string, + parseContent: ContentParser +): OurCommon.Either { + return parseElementContentInNamespace( + cursor, localName, parseContent, NAMESPACE + ); +} + +function parseElementContentInNamespace( + cursor: XmlCursor, + localName: string, + parseContent: ContentParser, + expectedNamespace: string +): OurCommon.Either { + const parsedOrError = parseContent(cursor); + if (parsedOrError.error !== null) { + return parsedOrError; + } + + const closeError = consumeCloseTagInNamespace( + cursor, localName, expectedNamespace + ); + if (closeError !== null) { + return new OurCommon.Either(null, closeError); + } + + return parsedOrError; +} + +/** + * Read the next XML element from `cursor`, expecting it to be named + * `expectedLocalName`, parse its content with `parseContent` and consume + * the matching closing element. + * + * This is what an item of a list or of a tuple is parsed with -- a scalar item + * in an element tagged `"v"`, `"v1"`, `"v2"`, *etc.*, and an item whose concrete + * class is statically known in an element named after that class. Rejecting + * an unexpected element on its local name alone spares us parsing its full + * (possibly deeply nested) content only to discover the mismatch afterwards. + * + * @param cursor - to read from + * @param expectedLocalName - the expected local name of the element + * @param parseContent - parses the content of the element + * @returns parsed value, or an error + * @typeParam T - type of the parsed value + */ +export function parseNamedElement( + cursor: XmlCursor, + expectedLocalName: string, + parseContent: ContentParser +): OurCommon.Either { + return parseNamedElementInNamespace( + cursor, expectedLocalName, parseContent, NAMESPACE + ); +} + +function parseNamedElementInNamespace( + cursor: XmlCursor, + expectedLocalName: string, + parseContent: ContentParser, + expectedNamespace: string +): OurCommon.Either { + const startTagOrError = readNextOpenTagInNamespace( + cursor, expectedNamespace + ); + if (startTagOrError.error !== null) { + return new OurCommon.Either( + null, + startTagOrError.error + ); + } + const startTag = startTagOrError.mustValue(); + + const observedLocalName = localNameOfTag(startTag.tag); + if (observedLocalName !== expectedLocalName) { + return newDeserializationError( + `Expected the element '${expectedLocalName}', ` + + `but got '${observedLocalName}'` + ); + } + + cursor.advance(); + + return parseElementContentInNamespace( + cursor, expectedLocalName, parseContent, expectedNamespace + ); +} + +/** + * Parse a sequence of list items from `cursor`, stopping (without consuming) + * at the first closing element. + * + * The caller is expected to read and verify the property's own closing + * element afterwards. + * + * @param cursor - to read from + * @param parseItem - parses a single list item + * @returns the parsed items, or an error + * @typeParam T - type of a single list item + */ +export function parseList( + cursor: XmlCursor, + parseItem: ContentParser +): OurCommon.Either, DeserializationError> { + const items = new Array(); + let itemIndex = 0; + + cursor.skipIgnorable(); + // eslint-disable-next-line no-constant-condition + while (true) { + const maybeClose = cursor.current(); + if (maybeClose === null) { + return newDeserializationError>( + "Expected an XML element corresponding to a list item " + + "or property closing element, but got end of token stream" + ); + } + + if (maybeClose instanceof CloseTagToken) { + break; + } + + // NOTE (mristin): + // Anything but an element, *e.g.*, a text, is reported at the list itself + // and not at an item, as there is no item element to point to. + if (!(maybeClose instanceof OpenTagToken)) { + return newDeserializationError>( + "Expected only item elements and whitespace text, " + + `but got token kind: ${maybeClose.kind}` + ); + } + + const itemOrError = parseItem(cursor); + if (itemOrError.error !== null) { + itemOrError.error.path.prepend(new IndexSegment(itemIndex)); + return new OurCommon.Either, DeserializationError>( + null, + itemOrError.error + ); + } + + items.push(itemOrError.mustValue()); + itemIndex++; + cursor.skipIgnorable(); + } + + return new OurCommon.Either, DeserializationError>(items, null); +} + +/** + * Cursor over parsed XML SAX tokens. + */ +export class XmlCursor { + private readonly _tokens: Array; + private _index = 0; + + constructor(tokens: Array) { + this._tokens = tokens; + } + + current(): XmlAnyToken | null { + if (this._index >= this._tokens.length) { + return null; + } + return this._tokens[this._index]; + } + + advance(): void { + if (this._index < this._tokens.length) { + this._index++; + } + } + + skipIgnorable(): void { + // eslint-disable-next-line no-constant-condition + while (true) { + const token = this.current(); + if (token === null) { + break; + } + + if (token instanceof CommentToken) { + this.advance(); + continue; + } + + if (token instanceof TextToken || token instanceof CdataToken) { + if (token.text.trim().length === 0) { + this.advance(); + continue; + } + } + + break; + } + } +} + +export function tokenizeXml( + xml: string +): OurCommon.Either, DeserializationError> { + const parser = new XmlSaxParser({ allowDoctype: false, xmlns: true }); + const tokens = new Array(); + + try { + for (const token of parser.feed(xml)) { + tokens.push(token); + } + for (const token of parser.close()) { + if (!(token instanceof EndToken)) { + tokens.push(token); + } + } + } catch (error) { + return newDeserializationError>( + `Failed to parse XML: ${error}` + ); + } + + return new OurCommon.Either, DeserializationError>( + tokens, + null + ); +} + +export function readRequiredRootOpenTag( + cursor: XmlCursor +): OurCommon.Either { + // NOTE (mristin): + // We skip the prolog before the root element, *i.e.*, the XML declaration, + // the processing instructions, the comments and the whitespace. + cursor.skipIgnorable(); + while (cursor.current() instanceof ProcessingInstructionToken) { + cursor.advance(); + cursor.skipIgnorable(); + } + + const token = cursor.current(); + if (token === null) { + return newDeserializationError( + "Expected a root XML element, but got an empty token stream" + ); + } + + if (!(token instanceof OpenTagToken)) { + return newDeserializationError( + `Expected a root XML start element, but got token kind: ${token.kind}` + ); + } + + const namespaceError = checkExpectedOpenTagNamespace(token, NAMESPACE); + if (namespaceError !== null) { + return new OurCommon.Either( + null, + namespaceError + ); + } + + cursor.advance(); + + return new OurCommon.Either( + token, + null + ); +} + +/** + * Match a run of the four characters which XML calls whitespace. + */ +const WHITESPACE_RUN = /[ \t\n\r]+/g; + +/** + * Normalize `text` the way `whiteSpace="collapse"` prescribes. + * + * Every atomic XSD type except a string, and every type derived from one by + * restriction, fixes `whiteSpace` to `collapse`, and a schema author can not + * change it. A tab, a line feed and a carriage return each become a space, + * a run of spaces becomes one space, and the leading and trailing spaces go. + * Only the result of that is a lexical representation to be matched. + * + * Mind that this strips only the whitespace *around* the value: a space + * within it survives as a single space, so `2 3` becomes `2 3`, which is + * still no number. + * + * See: https://www.w3.org/TR/xmlschema-2/#rf-whiteSpace + */ +export function collapseWhitespace(text: string): string { + return text.replace(WHITESPACE_RUN, " ").trim(); +} + +/** + * Tell whether `text` is a lexical form of `xs:base64Binary`. + * + * The whitespace is expected to be gone already. What is left has to match + * `(B64 B64 B64 B64)* ((B64 B64 B64 B64) | (B64 B64 B16 "=") | (B64 B04 "=="))?` + * -- a length which is a multiple of four, the alphabet and nothing else, + * an equals sign only at the very end, and, easily missed, a constrained + * character *before* the padding, as the bits which the padding drops have + * to be zero. + * + * The decoders do not agree on any of this, so every target does the same + * check of its own and refuses the same texts. + * + * See: https://www.w3.org/TR/xmlschema-2/#base64Binary + */ +export function matchesXsBase64Binary(text: string): boolean { + if (text.length % 4 !== 0) { + return false; + } + + if (text.length === 0) { + return true; + } + + let pads = 0; + if (text[text.length - 1] === "=") { + pads = 1; + if (text[text.length - 2] === "=") { + pads = 2; + } + } + + for (let i = 0; i < text.length - pads; i++) { + const character = text[i]; + const inAlphabet = + (character >= "A" && character <= "Z") || + (character >= "a" && character <= "z") || + (character >= "0" && character <= "9") || + character === "+" || + character === "/"; + if (!inAlphabet) { + return false; + } + } + + // NOTE (mristin): + // Only these sixteen characters leave the two dropped bits at zero, and + // only these four leave the four dropped bits at zero. + if (pads === 1) { + return "AEIMQUYcgkosw048".includes(text[text.length - 2]); + } + + if (pads === 2) { + return "AQgw".includes(text[text.length - 3]); + } + + return true; +} + +/** + * Drop every whitespace character of `text`. + * + * This is what `xs:base64Binary` needs: it allows whitespace between + * the characters and not only around them, so collapsing is not enough -- + * the decoder accepts none of it. + */ +export function removeWhitespace(text: string): string { + return text.replace(WHITESPACE_RUN, ""); +} + +/** + * Consume the text (or CDATA) content at `cursor`, if any. + * + * The caller is responsible for reading and verifying the closing element + * afterwards. + */ +export function parseTextContent(cursor: XmlCursor): string { + cursor.skipIgnorable(); + + let text = ""; + const maybeText = cursor.current(); + if (maybeText instanceof TextToken || maybeText instanceof CdataToken) { + text = maybeText.text; + cursor.advance(); + cursor.skipIgnorable(); + } + + return text; +} + +/** + * Write the content of an XML element -- everything between its opening and its + * closing tag -- into `parts`. + * + * @remarks + * + * This is the one shape which every writer wears, so that a writer can be given + * to another writer as its item writer. The framing of the element around such + * a content is written by {@link writeElement}, and only there. + * + * The content is pushed as one or more separate entries instead of being + * concatenated as it is produced, so that the single `parts.join("")` at the very + * end copies every piece of text exactly once, however deeply it is nested. + */ +export type ContentWriter = (parts: Array, value: T) => void; + +/** + * Write `value` as the XML element `localName`, its content written + * by `writeContent`. + * + * @remarks + * + * The root element, and only the root element, declares the XML namespace. It is + * by definition the first element to be written, so `parts` is still empty when + * we push its opening tag, and we need no flag threaded through the writers to + * tell it apart. + */ +export function writeElement( + parts: Array, + localName: string, + value: T, + writeContent: ContentWriter +): void { + parts.push( + parts.length === 0 + ? `<${localName} xmlns="${NAMESPACE}">` + : `<${localName}>` + ); + writeContent(parts, value); + parts.push(``); +} + +/** + * Write `value`, a property of the instance being serialized, as the XML element + * `elementName`. + * + * @remarks + * + * This is {@link writeElement} plus the reporting: a failure anywhere beneath + * this property is reported at a path which begins with the property. The framing + * of an *item* of a list or of a tuple deliberately goes through + * {@link writeElement} instead, as an item is reported by its index. + * + * The path names the TypeScript property, and not the XML element: a failed + * serialization is reported on an *instance*, which the caller holds, and not on + * a document which has not been written yet. The two names part company only + * where the meta-model prescribes a serialization name of its own, so + * `propertyName` falls back to `elementName` and is spelled out at those + * properties alone. + */ +export function writeProperty( + parts: Array, + elementName: string, + value: T, + writeContent: ContentWriter, + propertyName: string = elementName +): void { + try { + writeElement(parts, elementName, value, writeContent); + } catch (error) { + if (error instanceof SerializationError) { + error.prependProperty(propertyName); + } + throw error; + } +} + +/** + * Write `value`, a property of the instance being serialized, as the XML element + * `elementName` if it has been given, and write nothing at all otherwise. + */ +export function writeOptionalProperty( + parts: Array, + elementName: string, + value: T | null, + writeContent: ContentWriter, + propertyName: string = elementName +): void { + if (value !== null) { + writeProperty(parts, elementName, value, writeContent, propertyName); + } +} + +/** + * Write the items of `values`, each as its own whole XML element. + * + * @remarks + * + * The index is advanced only after an item has been written, so that a failure is + * reported at the item which actually failed. + */ +export function writeList( + parts: Array, + values: Array, + writeItemElement: ContentWriter +): void { + let index = 0; + try { + for (const value of values) { + writeItemElement(parts, value); + index++; + } + } catch (error) { + if (error instanceof SerializationError) { + error.prependIndex(index); + } + throw error; + } +} + +export function escapeXmlText(text: string): string { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlization.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlization.ts new file mode 100644 index 000000000..e661b15aa --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/src/xmlization.ts @@ -0,0 +1,1274 @@ +/** + * Provide de/serialization of meta-model classes to/from XML. + * + * The implementation is incremental and follows a SAX-style parsing approach. + * + * @remarks + * + * The primitives which every reader and writer of XML shares -- the cursor, the + * checks of the namespace and the consumption of the tags -- live in + * the `xmlcommon` module, and so do the error classes and the path, which this + * module re-exports so that they stay where a caller has always found them. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurCommon from "./common"; +import * as OurTypes from "./types"; +import * as OurStringification from "./stringification"; + +import { + ContentParser, + DeserializationError, + ElementSegment, + SerializationError, + XmlCursor, + collapseWhitespace, + consumeCloseTag, + currentTokenKind, + escapeXmlText, + localNameOfTag, + matchesXsBase64Binary, + newDeserializationError, + nextPropertyOpenTag, + parseElementContent, + parseList, + parseNamedElement, + parseTextContent, + readNextOpenTag, + readRequiredRootOpenTag, + removeWhitespace, + tokenizeXml, + writeElement, + writeList, + writeOptionalProperty, + writeProperty +} from "./xmlcommon"; + +export { + DeserializationError, + ElementSegment, + IndexSegment, + KeySegment, + Path, + SerializationError +} from "./xmlcommon"; +export type { Segment } from "./xmlcommon"; + +/** + * Read the next XML element from `cursor` and parse it with the parser which + * `parsersByLocalName` gives for the element's local name. + * + * An abstract class, a concrete class with descendants and a named union all + * prescribe no element tag of their own, so the tag is what tells us which + * parser to use. The set of local names which are accepted is the only thing + * which distinguishes one such dispatch from another, so it is the only thing + * which is generated -- the reading itself lives here. + * + * @param cursor - to read from + * @param expectedWhat - what we expected to read, for the error message + * @param parsersByLocalName - parser of the content, by the element's local name + * @returns parsed instance, or an error + * @typeParam T - type of the parsed instance + */ +function dispatchParseElement( + cursor: XmlCursor, + expectedWhat: string, + parsersByLocalName: ReadonlyMap> +): OurCommon.Either { + const startTagOrError = readNextOpenTag(cursor); + if (startTagOrError.error !== null) { + return new OurCommon.Either( + null, + startTagOrError.error + ); + } + + const localName = localNameOfTag(startTagOrError.mustValue().tag); + + const parseContent = parsersByLocalName.get(localName); + if (parseContent === undefined) { + return newDeserializationError( + `Expected an instance of ${expectedWhat}, but got: ${localName}` + ); + } + + cursor.advance(); + + return parseElementContent(cursor, localName, parseContent); +} + +/** + * Wrap `parseElement` so that its errors are reported at the element it parses. + * + * We use it where an instance is its own element, at the root or nested in + * a property, so that the path names the element of the instance, *e.g.*, + * `someRoot/property/someClass/someProperty`, as in the other SDKs. The items of + * a list and of a tuple are not wrapped, as their index already selects + * the element of the instance. + * + * @param parseElement - to parse the element whole, the tags included + * @returns the wrapped parser + * @typeParam T - type of the parsed instance + */ +function namingElement( + parseElement: ContentParser +): ContentParser { + return (cursor: XmlCursor) => { + const startTagOrError = readNextOpenTag(cursor); + const localName = + startTagOrError.error === null + ? localNameOfTag(startTagOrError.mustValue().tag) + : null; + + const valueOrError = parseElement(cursor); + if (valueOrError.error !== null && localName !== null) { + valueOrError.error.path.prepend(new ElementSegment(localName)); + } + return valueOrError; + }; +} + +/** + * Report that the property `localName` occurred more than once. + * + * The check itself sits in the property loop, right in front of the parse, since + * only the loop knows whether the property's variable has been set already. This + * is only the error, so that the message is written once instead of at every one + * of the property cases. + */ +function duplicatePropertyError(localName: string): DeserializationError { + return new DeserializationError( + "Property " + localName + " occurred more than once" + ); +} + +function parse_bool( + cursor: XmlCursor +): OurCommon.Either { + const text = collapseWhitespace(parseTextContent(cursor)); + + if (text === "true" || text === "1") { + return new OurCommon.Either(true, null); + } + if (text === "false" || text === "0") { + return new OurCommon.Either(false, null); + } + + return newDeserializationError( + `Expected xs:boolean text, but got: ${text}` + ); +} + +function parse_int( + cursor: XmlCursor +): OurCommon.Either { + const text = collapseWhitespace(parseTextContent(cursor)); + + if (!/^[+-]?\d+$/.test(text)) { + return newDeserializationError( + `Expected integer text, but got: ${text}` + ); + } + + const value = Number(text); + + // NOTE (mristin): + // An integer is a ``number`` in TypeScript, and a ``number`` holds only + // the integers up to 2^53 - 1 exactly. Beyond that ``Number`` rounds + // silently -- 9007199254740993 comes back as 9007199254740992, and + // 9223372036854775807 as 9223372036854776000, which is a perfectly + // well-formed but *different* xs:long. We refuse instead of corrupting. + // + // ``Number.isSafeInteger`` covers ``Number.isInteger`` as well, so + // a non-integer is refused here too. + if (!Number.isSafeInteger(value)) { + return newDeserializationError( + `Expected an integer within the safe range of a number, ` + + `but got: ${text}` + ); + } + + return new OurCommon.Either(value, null); +} + +function parse_float( + cursor: XmlCursor +): OurCommon.Either { + const text = collapseWhitespace(parseTextContent(cursor)); + + // NOTE (mristin): + // `+INF` is read although it is written as `INF`: XSD 1.1 admits it, its + // production being `(\+|-)?INF`, and being liberal in what we accept + // costs nothing here. + if (text === "INF" || text === "+INF") { + return new OurCommon.Either(Infinity, null); + } + if (text === "-INF") { + return new OurCommon.Either(-Infinity, null); + } + if (text === "NaN") { + return new OurCommon.Either(NaN, null); + } + + // NOTE (mristin): + // ``Number`` is far too permissive to be trusted with the text: it reads + // an empty string and a run of whitespace as 0, a hexadecimal, binary or + // octal prefix as the number it spells -- ``0x10`` as 16 -- and + // ``Infinity`` as an infinity, none of which is a valid ``xs:double``. + // + // See: https://www.w3.org/TR/xmlschema-2/#double + if (!/^(\+|-)?([0-9]+(\.[0-9]*)?|\.[0-9]+)([Ee](\+|-)?[0-9]+)?$/.test(text)) { + return newDeserializationError( + `Expected xs:double text, but got: ${text}` + ); + } + + const value = Number(text); + if (Number.isNaN(value)) { + return newDeserializationError( + `Expected xs:double text, but got: ${text}` + ); + } + + return new OurCommon.Either(value, null); +} + +function parse_str( + cursor: XmlCursor +): OurCommon.Either { + return new OurCommon.Either( + parseTextContent(cursor), + null + ); +} + +function parse_bytes( + cursor: XmlCursor +): OurCommon.Either { + // NOTE (mristin): + // ``xs:base64Binary`` allows whitespace between the characters, and not + // only around them, while the decoder accepts none of it. So every + // whitespace character is dropped, and not merely collapsed. + // + // See: https://www.w3.org/TR/xmlschema-2/#base64Binary + const text = removeWhitespace(parseTextContent(cursor)); + + if (!matchesXsBase64Binary(text)) { + return newDeserializationError( + `Expected a text as base64-encoded bytes, but got: ${text}` + ); + } + + const decodedOrError = OurCommon.base64Decode(text); + if (decodedOrError.error !== null) { + return newDeserializationError( + decodedOrError.error + ); + } + + return new OurCommon.Either( + decodedOrError.mustValue(), + null + ); +} + +function parseElement_SpecialItem( + cursor: XmlCursor +): OurCommon.Either { + return parseNamedElement( + cursor, + "specialItem", + parseSpecialItemFromSequence + ); +} + +function parse_ListOf_IItem( + cursor: XmlCursor +): OurCommon.Either, DeserializationError> { + return parseList( + cursor, + dispatchParseItemElement + ); +} + +function parse_ListOf_SomeItem( + cursor: XmlCursor +): OurCommon.Either, DeserializationError> { + return parseList( + cursor, + dispatchParseSomeItemElement + ); +} + +function parse_ListOf_SpecialItem( + cursor: XmlCursor +): OurCommon.Either, DeserializationError> { + return parseList( + cursor, + parseElement_SpecialItem + ); +} + +/** + * Parse the sequence of properties of an instance + * of {@link types!SomeItem}. + * + * The opening tag is expected to have been already read by the caller, and + * the caller is expected to read and verify the corresponding closing tag + * after this function returns successfully. This is the contract of + * a `ContentParser`, so this function is used as one wherever an instance + * of {@link types!SomeItem} is embedded. + */ +function parseSomeItemFromSequence( + cursor: XmlCursor +): OurCommon.Either { + let theName: string | null = null; + let theNickname: string | null = null; + + const className = OurTypes.SomeItem.name; + + cursor.skipIgnorable(); + // eslint-disable-next-line no-constant-condition + while (true) { + const nextTagOrError = nextPropertyOpenTag(cursor, className); + if (nextTagOrError === null) { + break; + } + if (nextTagOrError instanceof DeserializationError) { + return new OurCommon.Either( + null, + nextTagOrError + ); + } + + const propertyLocalName = localNameOfTag(nextTagOrError.tag); + + let propertyError: DeserializationError | null = null; + switch (propertyLocalName) { + case "name": { + if (theName !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_str + ); + propertyError = parsed.error; + theName = parsed.value; + break; + } + + case "nickname": { + if (theNickname !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_str + ); + propertyError = parsed.error; + theNickname = parsed.value; + break; + } + + default: { + propertyError = new DeserializationError( + `Unexpected XML property: ${propertyLocalName}` + ); + break; + } + } + + if (propertyError !== null) { + propertyError.path.prepend(new ElementSegment(propertyLocalName)); + return new OurCommon.Either( + null, + propertyError + ); + } + + cursor.skipIgnorable(); + } + + if (theName === null) { + return newDeserializationError( + "The required property 'name' is missing" + ); + } + + const instance = new OurTypes.SomeItem( + theName, + theNickname + ); + return new OurCommon.Either( + instance, + null + ); +} + +/** + * Parse the sequence of properties of an instance + * of {@link types!SpecialItem}. + * + * The opening tag is expected to have been already read by the caller, and + * the caller is expected to read and verify the corresponding closing tag + * after this function returns successfully. This is the contract of + * a `ContentParser`, so this function is used as one wherever an instance + * of {@link types!SpecialItem} is embedded. + */ +function parseSpecialItemFromSequence( + cursor: XmlCursor +): OurCommon.Either { + let theName: string | null = null; + let theNickname: string | null = null; + let theLevel: number | null = null; + + const className = OurTypes.SpecialItem.name; + + cursor.skipIgnorable(); + // eslint-disable-next-line no-constant-condition + while (true) { + const nextTagOrError = nextPropertyOpenTag(cursor, className); + if (nextTagOrError === null) { + break; + } + if (nextTagOrError instanceof DeserializationError) { + return new OurCommon.Either( + null, + nextTagOrError + ); + } + + const propertyLocalName = localNameOfTag(nextTagOrError.tag); + + let propertyError: DeserializationError | null = null; + switch (propertyLocalName) { + case "name": { + if (theName !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_str + ); + propertyError = parsed.error; + theName = parsed.value; + break; + } + + case "nickname": { + if (theNickname !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_str + ); + propertyError = parsed.error; + theNickname = parsed.value; + break; + } + + case "level": { + if (theLevel !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_int + ); + propertyError = parsed.error; + theLevel = parsed.value; + break; + } + + default: { + propertyError = new DeserializationError( + `Unexpected XML property: ${propertyLocalName}` + ); + break; + } + } + + if (propertyError !== null) { + propertyError.path.prepend(new ElementSegment(propertyLocalName)); + return new OurCommon.Either( + null, + propertyError + ); + } + + cursor.skipIgnorable(); + } + + if (theName === null) { + return newDeserializationError( + "The required property 'name' is missing" + ); + } + + if (theLevel === null) { + return newDeserializationError( + "The required property 'level' is missing" + ); + } + + const instance = new OurTypes.SpecialItem( + theName, + theLevel, + theNickname + ); + return new OurCommon.Either( + instance, + null + ); +} + +/** + * Parse the sequence of properties of an instance + * of {@link types!Container}. + * + * The opening tag is expected to have been already read by the caller, and + * the caller is expected to read and verify the corresponding closing tag + * after this function returns successfully. This is the contract of + * a `ContentParser`, so this function is used as one wherever an instance + * of {@link types!Container} is embedded. + */ +function parseContainerFromSequence( + cursor: XmlCursor +): OurCommon.Either { + let theItems: Array | null = null; + let theSomeItems: Array | null = null; + let theSpecialItems: Array | null = null; + + const className = OurTypes.Container.name; + + cursor.skipIgnorable(); + // eslint-disable-next-line no-constant-condition + while (true) { + const nextTagOrError = nextPropertyOpenTag(cursor, className); + if (nextTagOrError === null) { + break; + } + if (nextTagOrError instanceof DeserializationError) { + return new OurCommon.Either( + null, + nextTagOrError + ); + } + + const propertyLocalName = localNameOfTag(nextTagOrError.tag); + + let propertyError: DeserializationError | null = null; + switch (propertyLocalName) { + case "items": { + if (theItems !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_ListOf_IItem + ); + propertyError = parsed.error; + theItems = parsed.value; + break; + } + + case "someItems": { + if (theSomeItems !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_ListOf_SomeItem + ); + propertyError = parsed.error; + theSomeItems = parsed.value; + break; + } + + case "specialItems": { + if (theSpecialItems !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parse_ListOf_SpecialItem + ); + propertyError = parsed.error; + theSpecialItems = parsed.value; + break; + } + + default: { + propertyError = new DeserializationError( + `Unexpected XML property: ${propertyLocalName}` + ); + break; + } + } + + if (propertyError !== null) { + propertyError.path.prepend(new ElementSegment(propertyLocalName)); + return new OurCommon.Either( + null, + propertyError + ); + } + + cursor.skipIgnorable(); + } + + if (theItems === null) { + return newDeserializationError( + "The required property 'items' is missing" + ); + } + + if (theSomeItems === null) { + return newDeserializationError( + "The required property 'someItems' is missing" + ); + } + + const instance = new OurTypes.Container( + theItems, + theSomeItems, + theSpecialItems + ); + return new OurCommon.Either( + instance, + null + ); +} + +/** + * Parse the sequence of properties of an instance + * of {@link types!Wrapper}. + * + * The opening tag is expected to have been already read by the caller, and + * the caller is expected to read and verify the corresponding closing tag + * after this function returns successfully. This is the contract of + * a `ContentParser`, so this function is used as one wherever an instance + * of {@link types!Wrapper} is embedded. + */ +function parseWrapperFromSequence( + cursor: XmlCursor +): OurCommon.Either { + let theContainer: OurTypes.Container | null = null; + + const className = OurTypes.Wrapper.name; + + cursor.skipIgnorable(); + // eslint-disable-next-line no-constant-condition + while (true) { + const nextTagOrError = nextPropertyOpenTag(cursor, className); + if (nextTagOrError === null) { + break; + } + if (nextTagOrError instanceof DeserializationError) { + return new OurCommon.Either( + null, + nextTagOrError + ); + } + + const propertyLocalName = localNameOfTag(nextTagOrError.tag); + + let propertyError: DeserializationError | null = null; + switch (propertyLocalName) { + case "container": { + if (theContainer !== null) { + propertyError = duplicatePropertyError(propertyLocalName); + break; + } + + const parsed = parseElementContent( + cursor, + propertyLocalName, + parseContainerFromSequence + ); + propertyError = parsed.error; + theContainer = parsed.value; + break; + } + + default: { + propertyError = new DeserializationError( + `Unexpected XML property: ${propertyLocalName}` + ); + break; + } + } + + if (propertyError !== null) { + propertyError.path.prepend(new ElementSegment(propertyLocalName)); + return new OurCommon.Either( + null, + propertyError + ); + } + + cursor.skipIgnorable(); + } + + if (theContainer === null) { + return newDeserializationError( + "The required property 'container' is missing" + ); + } + + const instance = new OurTypes.Wrapper( + theContainer + ); + return new OurCommon.Either( + instance, + null + ); +} + +/** + * Write the properties of an instance + * of {@link types!SomeItem}, and neither the opening + * nor the closing tag of the element which holds them -- which is the contract of + * a `ContentWriter`, so this function is used as one. + */ +function writeSomeItemAsSequence( + parts: Array, + that: OurTypes.SomeItem +): void { + writeProperty( + parts, + "name", + that.name, + write_str + ); + writeOptionalProperty( + parts, + "nickname", + that.nickname, + write_str + ); +} + +/** + * Write the properties of an instance + * of {@link types!SpecialItem}, and neither the opening + * nor the closing tag of the element which holds them -- which is the contract of + * a `ContentWriter`, so this function is used as one. + */ +function writeSpecialItemAsSequence( + parts: Array, + that: OurTypes.SpecialItem +): void { + writeProperty( + parts, + "name", + that.name, + write_str + ); + writeOptionalProperty( + parts, + "nickname", + that.nickname, + write_str + ); + writeProperty( + parts, + "level", + that.level, + write_int + ); +} + +/** + * Write the properties of an instance + * of {@link types!Container}, and neither the opening + * nor the closing tag of the element which holds them -- which is the contract of + * a `ContentWriter`, so this function is used as one. + */ +function writeContainerAsSequence( + parts: Array, + that: OurTypes.Container +): void { + writeProperty( + parts, + "items", + that.items, + writeListOfInstances + ); + writeProperty( + parts, + "someItems", + that.someItems, + writeListOfInstances + ); + writeOptionalProperty( + parts, + "specialItems", + that.specialItems, + writeListOfInstances + ); +} + +/** + * Write the properties of an instance + * of {@link types!Wrapper}, and neither the opening + * nor the closing tag of the element which holds them -- which is the contract of + * a `ContentWriter`, so this function is used as one. + */ +function writeWrapperAsSequence( + parts: Array, + that: OurTypes.Wrapper +): void { + writeProperty( + parts, + "container", + that.container, + writeContainerAsSequence + ); +} + +const PARSERS_OF_ITEM = new Map< + string, + ContentParser +>([ + ["someItem", parseSomeItemFromSequence], + ["specialItem", parseSpecialItemFromSequence] +]); + +/** + * Dispatch-parse an instance + * of {@link types!IItem} from the next + * XML element in `cursor`, based on the element's local name. + * + * @param cursor - to read from + * @returns the parsed instance, or an error + */ +function dispatchParseItemElement( + cursor: XmlCursor +): OurCommon.Either { + return dispatchParseElement( + cursor, + "IItem", + PARSERS_OF_ITEM + ); +} + +/** + * Parse an XML string as an instance + * of {@link types!IItem}. + * + * @param xml - XML string to parse + * @returns parsed instance, or an error + */ +export function itemFromXmlString( + xml: string +): OurCommon.Either { + if (xml.length === 0) { + return newDeserializationError( + "Expected an XML document, but got an empty string" + ); + } + + const tokensOrError = tokenizeXml(xml); + if (tokensOrError.error !== null) { + return new OurCommon.Either( + null, + tokensOrError.error + ); + } + + const cursor = new XmlCursor(tokensOrError.mustValue()); + + const instanceOrError = namingElement(dispatchParseItemElement)(cursor); + if (instanceOrError.error !== null) { + return instanceOrError; + } + + cursor.skipIgnorable(); + if (cursor.current() !== null) { + return newDeserializationError( + "Expected no tokens after the root XML element, but got token kind: " + + currentTokenKind(cursor) + ); + } + + return instanceOrError; +} + +const PARSERS_OF_SOME_ITEM = new Map< + string, + ContentParser +>([ + ["specialItem", parseSpecialItemFromSequence], + ["someItem", parseSomeItemFromSequence] +]); + +/** + * Dispatch-parse an instance + * of {@link types!SomeItem} from the next + * XML element in `cursor`, based on the element's local name. + * + * @param cursor - to read from + * @returns the parsed instance, or an error + */ +function dispatchParseSomeItemElement( + cursor: XmlCursor +): OurCommon.Either { + return dispatchParseElement( + cursor, + "SomeItem", + PARSERS_OF_SOME_ITEM + ); +} + +/** + * Parse an XML string as an instance + * of {@link types!SomeItem}. + * + * @param xml - XML string to parse + * @returns parsed instance, or an error + */ +export function someItemFromXmlString( + xml: string +): OurCommon.Either { + if (xml.length === 0) { + return newDeserializationError( + "Expected an XML document, but got an empty string" + ); + } + + const tokensOrError = tokenizeXml(xml); + if (tokensOrError.error !== null) { + return new OurCommon.Either( + null, + tokensOrError.error + ); + } + + const cursor = new XmlCursor(tokensOrError.mustValue()); + + const instanceOrError = namingElement(dispatchParseSomeItemElement)(cursor); + if (instanceOrError.error !== null) { + return instanceOrError; + } + + cursor.skipIgnorable(); + if (cursor.current() !== null) { + return newDeserializationError( + "Expected no tokens after the root XML element, but got token kind: " + + currentTokenKind(cursor) + ); + } + + return instanceOrError; +} + +const ROOT_DISPATCH_BY_LOCAL_NAME = new Map< + string, + ContentParser +>([ + ["someItem", parseSomeItemFromSequence], + ["specialItem", parseSpecialItemFromSequence], + ["container", parseContainerFromSequence], + ["wrapper", parseWrapperFromSequence] +]); + +/** + * Parse an XML string as a model instance. + * + * @param xml - XML string to parse + * @returns parsed model instance or an error + */ +export function fromXmlString( + xml: string +): OurCommon.Either { + if (xml.length === 0) { + return newDeserializationError( + "Expected an XML document, but got an empty string" + ); + } + + const tokensOrError = tokenizeXml(xml); + if (tokensOrError.error !== null) { + return new OurCommon.Either( + null, + tokensOrError.error + ); + } + + const cursor = new XmlCursor(tokensOrError.mustValue()); + + const rootOpenTagOrError = readRequiredRootOpenTag(cursor); + if (rootOpenTagOrError.error !== null) { + return new OurCommon.Either( + null, + rootOpenTagOrError.error + ); + } + + const rootOpenTag = rootOpenTagOrError.mustValue(); + const rootLocalName = localNameOfTag(rootOpenTag.tag); + + // NOTE (mristin): + // The errors within the root element are reported at it, as in the other SDKs. + const dispatch = ROOT_DISPATCH_BY_LOCAL_NAME.get(rootLocalName); + if (dispatch === undefined) { + const error = new DeserializationError( + `Unexpected root XML element: ${rootLocalName}` + ); + error.path.prepend(new ElementSegment(rootLocalName)); + return new OurCommon.Either( + null, + error + ); + } + + const instanceOrError = dispatch(cursor); + if (instanceOrError.error !== null) { + instanceOrError.error.path.prepend(new ElementSegment(rootLocalName)); + return instanceOrError; + } + + const closeError = consumeCloseTag(cursor, rootLocalName); + if (closeError !== null) { + closeError.path.prepend(new ElementSegment(rootLocalName)); + return new OurCommon.Either( + null, + closeError + ); + } + + cursor.skipIgnorable(); + if (cursor.current() !== null) { + return newDeserializationError( + "Expected no tokens after the root XML element, but got token kind: " + + currentTokenKind(cursor) + ); + } + + return instanceOrError; +} + +/** + * Write the instances of `values`, each as its own, self-describing XML element. + * + * @remarks + * + * Every item goes through {@link writeClass} whatever its declared type is, so + * this one writer serves every list of instances in the meta-model. + */ +function writeListOfInstances( + parts: Array, + values: Array +): void { + writeList(parts, values, writeClass); +} + +/** + * Write `that` as its own, self-describing XML element. + * + * @remarks + * + * Which element that is, is decided by the run-time type of `that`, so this one + * writer serves every abstract class, every named union, and the item of a list + * or of a tuple of any class at all. The reading, which has to decide what to + * construct before it has read anything, needs a dispatcher per interface instead. + */ +function writeClass(parts: Array, that: OurTypes.Class): void { + SERIALIZER.visitWithContext(that, parts); +} + +/** + * Write the literal `value` of the enumeration `enumerationName` as the content + * of an XML element. + * + * @remarks + * + * We deliberately go through the `toString` of the stringification module, which + * gives out `null` for a literal it does not know, and not through its `mustToString`, + * which throws an error of its own. An instance carrying a literal outside its + * enumeration is exactly the kind of failure this module reports with a path. + */ +function writeEnumerationContent( + parts: Array, + value: T, + enumerationName: string, + toString: (value: T) => string | null +): void { + const text = toString(value); + if (text === null) { + throw new SerializationError( + `Invalid literal of ${enumerationName}: ${value}` + ); + } + + parts.push(escapeXmlText(text)); +} + +function write_bool( + parts: Array, + value: boolean +): void { + parts.push(value ? "true" : "false"); +} + +function write_int( + parts: Array, + value: number +): void { + // NOTE (mristin): + // Beyond 2^53 - 1 a ``number`` no longer holds every integer, so the value + // we were handed has already lost its identity -- writing it out would + // record a different xs:long without a word. ``Number.isSafeInteger`` + // covers ``Number.isInteger`` as well. + if (!Number.isSafeInteger(value)) { + throw new SerializationError( + `Expected an integer within the safe range of a number, ` + + `but got: ${value}` + ); + } + + parts.push(`${value}`); +} + +function write_float( + parts: Array, + value: number +): void { + if (Number.isNaN(value)) { + parts.push("NaN"); + } else if (value === Infinity) { + parts.push("INF"); + } else if (value === -Infinity) { + parts.push("-INF"); + } else if (Object.is(value, -0)) { + // NOTE (mristin): + // ``${-0}`` is "0", so the sign would be dropped, and a negative zero + // would come back as a positive one. ``-0`` is a valid xs:double, and + // the other SDKs keep the sign, so we keep it too. Mind that + // ``value === -0`` is true for a positive zero as well, which is why + // this asks ``Object.is``. + parts.push("-0"); + } else { + parts.push(`${value}`); + } +} + +function write_str( + parts: Array, + value: string +): void { + parts.push(escapeXmlText(value)); +} + +function write_bytes( + parts: Array, + value: Uint8Array +): void { + parts.push(escapeXmlText(OurCommon.base64Encode(value))); +} + +/** + * Write the XML element of an instance, dispatching on its run-time type. + * + * Each method writes the whole element -- the tags included -- since the element + * is picked by the run-time type, which this dispatch has just established. The + * properties are written by the corresponding module-level `write{Cls}AsSequence`, + * which is the content writer of that very element. + */ +class Serializer extends OurTypes.AbstractVisitorWithContext> { + visitSomeItemWithContext( + that: OurTypes.SomeItem, + parts: Array + ): void { + writeElement( + parts, + "someItem", + that, + writeSomeItemAsSequence + ); + } + + visitSpecialItemWithContext( + that: OurTypes.SpecialItem, + parts: Array + ): void { + writeElement( + parts, + "specialItem", + that, + writeSpecialItemAsSequence + ); + } + + visitContainerWithContext( + that: OurTypes.Container, + parts: Array + ): void { + writeElement( + parts, + "container", + that, + writeContainerAsSequence + ); + } + + visitWrapperWithContext( + that: OurTypes.Wrapper, + parts: Array + ): void { + writeElement( + parts, + "wrapper", + that, + writeWrapperAsSequence + ); + } +} + +const SERIALIZER = new Serializer(); + +/** + * Serialize a model instance as an XML string. + * + * @param that - model instance to serialize + * @returns serialized XML string + * @throws {@link SerializationError} if `that` can not be serialized, *e.g.*, if + * a property expected to be an integer holds a fractional number + */ +export function toXmlString(that: OurTypes.Class): string { + const parts = new Array(); + writeClass(parts, that); + return parts.join(""); +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64.spec.ts new file mode 100644 index 000000000..75ec0d285 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64.spec.ts @@ -0,0 +1,212 @@ +/** + * Test base64 encoding and decoding. + */ + +import * as OurCommon from "../src/common"; + +function testEncodeDecode(text: string, expectedEncoded: string): void { + const bytes = Uint8Array.from(text.split("").map((c) => c.charCodeAt(0))); + + const encoded = OurCommon.base64Encode(bytes); + expect(encoded).toEqual(expectedEncoded); + + const decodedOrError: OurCommon.Either = + OurCommon.base64Decode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); +} + +// NOTE (mristin): +// The following tests come from: +// https://www.rfc-editor.org/rfc/rfc4648#section-10 + +test("empty", () => { + testEncodeDecode("", ""); +}); + +test("Uint8Array from 'f' is encoded 'Zg=='", () => { + testEncodeDecode("f", "Zg=="); +}); + +test("Uint8Array from 'fo' is encoded 'Zm8='", () => { + testEncodeDecode("fo", "Zm8="); +}); + +test("Uint8Array from 'foo' is encoded 'Zm9v'", () => { + testEncodeDecode("foo", "Zm9v"); +}); + +test("Uint8Array from 'foob' is encoded 'Zm9vYg=='", () => { + testEncodeDecode("foob", "Zm9vYg=="); +}); + +test("Uint8Array from 'fooba' is encoded 'Zm9vYmE='", () => { + testEncodeDecode("fooba", "Zm9vYmE="); +}); + +test("Uint8Array from 'foobar' is encoded 'Zm9vYmFy'", () => { + testEncodeDecode("foobar", "Zm9vYmFy"); +}); + +test("unexpected padding in the middle", () => { + const encoded = "Zm9vYmFy"; + + for (let i = 0; i < encoded.length - 1; i++) { + const badEncoded = + encoded.substring(0, i) + "=" + encoded.substring(i + 1, encoded.length); + + // Test the test + expect(badEncoded.length).toEqual(encoded.length); + + const decodedOrError = OurCommon.base64Decode(badEncoded); + expect(decodedOrError.error).toEqual( + "Expected a valid character from " + + "base64-encoded string, but got at " + + `index ${i}: = (code: 61)` + ); + } +}); + +test("Bytes representing an invalid UTF-8 sequence encode OK", () => { + // NOTE (mristin): + // We simply add a test to make sure our implementation does not suffer from + // issues raised in: + // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings + + // From: + // https://stackoverflow.com/questions/1301402/example-invalid-utf8-string + const bytes = new Uint8Array([0xc3, 0x28]); + + // NOTE (mristin): + // This encoding was obtained by Python `base64` module. + const expectedEncoded = "wyg="; + + const encoded = OurCommon.base64Encode(bytes); + expect(encoded).toEqual(expectedEncoded); + + const decodedOrError: OurCommon.Either = + OurCommon.base64Decode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); +}); + +// NOTE (mristin): +// The following checks come from: https://eprint.iacr.org/2022/361.pdf + +test("'Hello' as 'SGVsbG8=' OK", () => { + testEncodeDecode("Hello", "SGVsbG8="); +}); + +test("our implementation suffers from padding inconsistency of 'Hello' as 'SGVsbG9='", () => { + // NOTE (mristin): + // This is not a test case, but we merely document that there is a possible + // attack vector where different strings encode the *same* byte sequence. + + const badEncoded = "SGVsbG9="; + + const decodedOrError = OurCommon.base64Decode(badEncoded); + expect(decodedOrError.mustValue()).toEqual( + new Uint8Array( + // "Hello" + [72, 101, 108, 108, 111] + ) + ); + expect(decodedOrError.error).toBeNull(); +}); + +test("table from rickkas7/Base64RK", () => { + // NOTE (mristin): + // The following tests have been scraped from: + // https://github.com/rickkas7/Base64RK/blob/183d20e62b96ef9b0230c80c7a172715ca6f661e/test/unit-test/unit-test.cpp + + const arraysExpectedEncodeds: Array<[Array, string]> = [ + [[28], "HA=="], + [[174, 208], "rtA="], + [[50, 224, 208], "MuDQ"], + [[45, 18, 235, 6], "LRLrBg=="], + [[117, 199, 153, 221, 160], "dceZ3aA="], + [[141, 12, 188, 48, 173, 45], "jQy8MK0t"], + [[69, 143, 148, 125, 213, 3, 148], "RY+UfdUDlA=="], + [[251, 162, 166, 60, 129, 131, 46, 13], "+6KmPIGDLg0="], + [[207, 40, 204, 254, 77, 71, 61, 8, 128], "zyjM/k1HPQiA"], + [[156, 150, 213, 174, 117, 59, 70, 220, 210, 63], "nJbVrnU7RtzSPw=="], + [[192, 54, 173, 19, 126, 84, 26, 250, 167, 5, 50], "wDatE35UGvqnBTI="], + [[20, 1, 15, 77, 224, 141, 153, 47, 186, 170, 200, 227], "FAEPTeCNmS+6qsjj"], + [ + [85, 166, 130, 169, 193, 117, 194, 81, 238, 64, 180, 228, 127], + "VaaCqcF1wlHuQLTkfw==" + ], + [ + [65, 130, 145, 17, 247, 99, 158, 36, 117, 8, 192, 1, 193, 234], + "QYKREfdjniR1CMABweo=" + ], + [ + [9, 55, 253, 179, 203, 93, 237, 58, 252, 200, 168, 145, 115, 53, 31], + "CTf9s8td7Tr8yKiRczUf" + ], + [ + [ + 254, 76, 140, 77, 16, 52, 119, 229, 186, 225, 109, 181, 134, 34, 229, 153, 235, + 1, 16, 126, 17, 14, 13, 12, 138, 248, 230, 60, 73, 128, 116, 204, 207 + ], + "/kyMTRA0d+W64W21hiLlmesBEH4RDg0MivjmPEmAdMzP" + ], + [ + [ + 155, 159, 208, 168, 221, 216, 113, 8, 58, 136, 77, 93, 86, 50, 80, 91, 14, 102, + 10, 54, 2, 158, 195, 75, 40, 245, 84, 177, 116, 54, 235, 217, 124, 232, 205, + 113, 111, 33, 231, 251, 246, 136, 41, 167, 201, 234, 20, 83, 225, 67, 131, 210, + 97, 194, 1, 252, 158, 253, 84, 135, 119, 1, 254, 11, 182, 208 + ], + "m5/QqN3YcQg6iE1dVjJQWw5mCjYCnsNLKPVUsXQ269l86M1xbyHn+/aIKafJ6hRT4UOD0mHCAfye/VSHdwH+C7bQ" + ], + [ + [ + 101, 43, 33, 221, 108, 128, 221, 221, 52, 11, 40, 166, 120, 198, 79, 0, 141, + 127, 183, 226, 197, 232, 52, 29, 136, 1, 16, 27, 216, 71, 149, 251, 131, 94, 22, + 118, 25, 255, 214, 119, 91, 182, 203, 228, 67, 22, 88, 180, 161, 42, 182, 125, + 183, 239, 186, 216, 23, 11, 17, 144, 124, 201, 177, 3, 83, 68, 131, 249, 52, + 189, 2, 28, 74, 172, 214, 117, 126, 249, 239, 90, 22, 204, 161, 243, 232, 166, + 103, 150, 20, 240, 60, 93, 187, 67, 57, 231, 1, 102, 221, 194, 76, 162, 8, 89, + 72, 150, 146, 247, 181, 134, 242, 205, 179, 19, 155, 68, 94, 82, 214, 56, 1, + 111, 109, 78, 155, 216, 73, 7, 80, 243 + ], + "ZSsh3WyA3d00CyimeMZPAI1/t+LF6DQdiAEQG9hHlfuDXhZ2Gf/Wd1u2y+RDFli0oSq2fbfvutgXCxGQfMmxA1NEg/k0vQIcSqzWdX7571oWzKHz6KZnlhTwPF27QznnAWbdwkyiCFlIlpL3tYbyzbMTm0ReUtY4AW9tTpvYSQdQ8w==" + ], + [ + [ + 177, 224, 149, 210, 54, 194, 123, 141, 157, 224, 99, 1, 189, 114, 37, 230, 250, + 149, 161, 158, 138, 226, 5, 18, 131, 169, 202, 130, 46, 109, 102, 35, 113, 199, + 109, 178, 98, 137, 249, 69, 227, 130, 232, 246, 105, 101, 97, 234, 120, 151, 31, + 41, 90, 20, 15, 246, 90, 214, 39, 139, 86, 52, 78, 174, 35, 95, 63, 226, 241, + 127, 164, 219, 174, 140, 255, 17, 120, 118, 97, 131, 133, 35, 231, 117, 215, 74, + 14, 110, 70, 42, 110, 7, 37, 234, 139, 210, 224, 36, 71, 227, 68, 155, 236, 191, + 61, 83, 11, 189, 88, 60, 208, 3, 109, 121, 32, 71, 169, 58, 125, 112, 116, 16, + 153, 200, 74, 242, 89, 141, 143, 25, 211, 92, 179, 227, 223, 183, 219, 18, 54, + 208, 80, 253, 11, 76, 69, 150, 4, 48, 238, 73, 165, 25, 133, 200, 80, 207, 84, + 182, 64, 251, 156, 76, 255, 112, 227, 235, 154, 29, 191, 86, 217, 118, 219, 51, + 38, 173, 6, 111, 14, 55, 195, 13, 45, 236, 66, 180, 39, 109, 110, 7, 47, 61, + 103, 100, 117, 55, 89, 35, 64, 106, 106, 42, 238, 37, 70, 20, 8, 128, 34, 131, + 56, 120, 103, 125, 149, 182, 130, 217, 38, 102, 24, 170, 111, 47, 113, 79, 142, + 4, 167, 80, 12, 174, 102, 74, 129, 109, 204, 12, 252, 200, 95, 97, 51, 215, 169, + 148, 191, 113, 155, 228, 145, 235, 35, 252, 53, 177 + ], + "seCV0jbCe42d4GMBvXIl5vqVoZ6K4gUSg6nKgi5tZiNxx22yYon5ReOC6PZpZWHqeJcfKVoUD/Za1ieLVjROriNfP+Lxf6Tbroz/EXh2YYOFI+d110oObkYqbgcl6ovS4CRH40Sb7L89Uwu9WDzQA215IEepOn1wdBCZyEryWY2PGdNcs+Pft9sSNtBQ/QtMRZYEMO5JpRmFyFDPVLZA+5xM/3Dj65odv1bZdtszJq0Gbw43ww0t7EK0J21uBy89Z2R1N1kjQGpqKu4lRhQIgCKDOHhnfZW2gtkmZhiqby9xT44Ep1AMrmZKgW3MDPzIX2Ez16mUv3Gb5JHrI/w1sQ==" + ] + ]; + + for (const [array, expectedEncoded] of arraysExpectedEncodeds) { + const bytes = new Uint8Array(array); + + const encoded = OurCommon.base64Encode(bytes); + expect(encoded).toEqual(expectedEncoded); + + const decodedOrError: OurCommon.Either = + OurCommon.base64Decode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); + } +}); diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64url.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64url.spec.ts new file mode 100644 index 000000000..9bd7d0d77 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.base64url.spec.ts @@ -0,0 +1,145 @@ +/** + * Test base64url encoding and decoding. + */ + +import * as OurCommon from "../src/common"; + +function testBase64UrlEncodeDecode(text: string, expectedEncoded: string): void { + const bytes = Uint8Array.from(text.split("").map((c) => c.charCodeAt(0))); + + const encoded = OurCommon.base64UrlEncode(bytes); + expect(encoded).toEqual(expectedEncoded); + + const decodedOrError: OurCommon.Either = + OurCommon.base64UrlDecode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); +} + +test("empty string", () => { + testBase64UrlEncodeDecode("", ""); +}); + +test("'f' is encoded 'Zg'", () => { + testBase64UrlEncodeDecode("f", "Zg"); +}); + +test("'fo' is encoded 'Zm8'", () => { + testBase64UrlEncodeDecode("fo", "Zm8"); +}); + +test("'foo' is encoded 'Zm9v'", () => { + testBase64UrlEncodeDecode("foo", "Zm9v"); +}); + +test("'foob' is encoded 'Zm9vYg'", () => { + testBase64UrlEncodeDecode("foob", "Zm9vYg"); +}); + +test("'fooba' is encoded 'Zm9vYmE'", () => { + testBase64UrlEncodeDecode("fooba", "Zm9vYmE"); +}); + +test("'foobar' is encoded 'Zm9vYmFy'", () => { + testBase64UrlEncodeDecode("foobar", "Zm9vYmFy"); +}); + +test("RFC 4648 test vectors", () => { + const testVectors: Array<[string, string]> = [ + ["", ""], + ["f", "Zg"], + ["fo", "Zm8"], + ["foo", "Zm9v"], + ["foob", "Zm9vYg"], + ["fooba", "Zm9vYmE"], + ["foobar", "Zm9vYmFy"] + ]; + + for (const [input, expected] of testVectors) { + testBase64UrlEncodeDecode(input, expected); + } +}); + +test("characters that differ from base64", () => { + const bytes = new Uint8Array([0x3e, 0x3f, 0xfc, 0xff]); + + const base64 = OurCommon.base64Encode(bytes); + const base64url = OurCommon.base64UrlEncode(bytes); + + expect(base64).toEqual("Pj/8/w=="); + expect(base64url).toEqual("Pj_8_w"); + expect(base64url).not.toContain("+"); + expect(base64url).not.toContain("/"); + expect(base64url).not.toContain("="); +}); + +test("decode with missing padding", () => { + const testCases = ["Zg", "Zm8", "Zm9vYg", "Zm9vYmE"]; + + for (const encoded of testCases) { + const decodedOrError = OurCommon.base64UrlDecode(encoded); + expect(decodedOrError.error).toBeNull(); + } +}); + +test("decode URL-safe characters", () => { + const encoded = "Pj_8_w"; + const decodedOrError = OurCommon.base64UrlDecode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(new Uint8Array([0x3e, 0x3f, 0xfc, 0xff])); +}); + +test("round-trip with binary data", () => { + const bytes = new Uint8Array(256); + for (let i = 0; i < 256; i++) { + bytes[i] = i; + } + + const encoded = OurCommon.base64UrlEncode(bytes); + const decodedOrError = OurCommon.base64UrlDecode(encoded); + + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); + expect(encoded).not.toContain("+"); + expect(encoded).not.toContain("/"); + expect(encoded).not.toContain("="); +}); + +test("specific byte sequences that produce URL-unsafe characters", () => { + const testCases: Array<[number[], string]> = [ + [[62], "Pg"], + [[63], "Pw"], + [[250], "-g"], // Base64 would start with `+` + [[251], "-w"], + [[252], "_A"], // Base64 would start with `/` + [[253], "_Q"], + [[254], "_g"], + [[255], "_w"], + [[62, 63], "Pj8"], + [[252, 253], "_P0"], + [[254, 255], "_v8"] + ]; + + for (const [numberArray, expectedEncoded] of testCases) { + const bytes = new Uint8Array(numberArray); + const encoded = OurCommon.base64UrlEncode(bytes); + + expect(encoded).toEqual(expectedEncoded); + + const decodedOrError = OurCommon.base64UrlDecode(encoded); + expect(decodedOrError.error).toBeNull(); + expect(decodedOrError.mustValue()).toEqual(bytes); + } +}); + +test("invalid characters in input", () => { + const invalidInputs = ["Zm9v+", "Zm9v/", "Zm9v=", "Zm9v YmFy", "Zm9v\n", "Zm9v\t"]; + + for (const invalid of invalidInputs) { + const decodedOrError = OurCommon.base64UrlDecode(invalid); + expect(decodedOrError.error).not.toBeNull(); + expect(decodedOrError.value).toBeNull(); + } +}); diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.stringHelpers.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.stringHelpers.spec.ts new file mode 100644 index 000000000..84ea2c591 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.stringHelpers.spec.ts @@ -0,0 +1,205 @@ +/** + * Test `len`, the slicing of strings and `find` as used in the transpiled code. + * + * @remarks + * The transpiled code follows the Python implementation, since Python is + * the language of the meta-model specifications. Hence, the lengths and + * the positions count the characters (code points). The expected values have + * been computed with Python, and all the SDKs test against the very same cases. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurCommon from "../src/common"; + +test("len('') gives 0: empty text", () => { + expect(OurCommon.lenStr("")).toStrictEqual(0); +}); + +test("len('abc') gives 3: ASCII text", () => { + expect(OurCommon.lenStr("abc")).toStrictEqual(3); +}); + +test("len('é-') gives 2: character of two UTF-8 bytes", () => { + expect(OurCommon.lenStr("é-")).toStrictEqual(2); +}); + +test("len('😀-') gives 2: character beyond the Basic Multilingual Plane", () => { + expect(OurCommon.lenStr("😀-")).toStrictEqual(2); +}); + +test("len('aé😀b😀') gives 5: mixed characters", () => { + expect(OurCommon.lenStr("aé😀b😀")).toStrictEqual(5); +}); + +test("'abcde'[1:3] gives 'bc': start and end", () => { + expect(OurCommon.sliceStr("abcde", 1, 3)).toStrictEqual("bc"); +}); + +test("'abcde'[:2] gives 'ab': no start", () => { + expect(OurCommon.sliceStr("abcde", 0, 2)).toStrictEqual("ab"); +}); + +test("'abcde'[3:] gives 'de': no end", () => { + expect(OurCommon.sliceStr("abcde", 3)).toStrictEqual("de"); +}); + +test("'abcde'[:] gives 'abcde': neither start nor end", () => { + expect(OurCommon.sliceStr("abcde", 0)).toStrictEqual("abcde"); +}); + +test("'abcde'[-2:] gives 'de': negative start counts from the end", () => { + expect(OurCommon.sliceStr("abcde", -2)).toStrictEqual("de"); +}); + +test("'abcde'[:-2] gives 'abc': negative end counts from the end", () => { + expect(OurCommon.sliceStr("abcde", 0, -2)).toStrictEqual("abc"); +}); + +test("'abcde'[-4:-1] gives 'bcd': negative start and end", () => { + expect(OurCommon.sliceStr("abcde", -4, -1)).toStrictEqual("bcd"); +}); + +test("'abcde'[-10:2] gives 'ab': negative start before the beginning is clamped", () => { + expect(OurCommon.sliceStr("abcde", -10, 2)).toStrictEqual("ab"); +}); + +test("'abcde'[2:10] gives 'cde': end beyond the end is clamped", () => { + expect(OurCommon.sliceStr("abcde", 2, 10)).toStrictEqual("cde"); +}); + +test("'abcde'[10:] gives '': start beyond the end gives empty", () => { + expect(OurCommon.sliceStr("abcde", 10)).toStrictEqual(""); +}); + +test("'abcde'[3:1] gives '': start after end gives empty", () => { + expect(OurCommon.sliceStr("abcde", 3, 1)).toStrictEqual(""); +}); + +test("'abcde'[-1:-3] gives '': negative start after negative end gives empty", () => { + expect(OurCommon.sliceStr("abcde", -1, -3)).toStrictEqual(""); +}); + +test("'abcde'[5:5] gives '': start equal to end gives empty", () => { + expect(OurCommon.sliceStr("abcde", 5, 5)).toStrictEqual(""); +}); + +test("''[0:0] gives '': empty text", () => { + expect(OurCommon.sliceStr("", 0, 0)).toStrictEqual(""); +}); + +test("''[-1:] gives '': negative start on empty text", () => { + expect(OurCommon.sliceStr("", -1)).toStrictEqual(""); +}); + +test("''[:5] gives '': end beyond the end of empty text", () => { + expect(OurCommon.sliceStr("", 0, 5)).toStrictEqual(""); +}); + +test("'é-x'[1:] gives '-x': after a character of two UTF-8 bytes", () => { + expect(OurCommon.sliceStr("é-x", 1)).toStrictEqual("-x"); +}); + +test("'aéb'[1:2] gives 'é': character of two UTF-8 bytes", () => { + expect(OurCommon.sliceStr("aéb", 1, 2)).toStrictEqual("é"); +}); + +test("'a😀b'[1:2] gives '😀': character beyond the Basic Multilingual Plane", () => { + expect(OurCommon.sliceStr("a😀b", 1, 2)).toStrictEqual("😀"); +}); + +test("'😀-x'[1:] gives '-x': after a character beyond the Basic Multilingual Plane", () => { + expect(OurCommon.sliceStr("😀-x", 1)).toStrictEqual("-x"); +}); + +test("'aé😀b😀'[-3:-1] gives '😀b': negative start on characters beyond ASCII", () => { + expect(OurCommon.sliceStr("aé😀b😀", -3, -1)).toStrictEqual("😀b"); +}); + +test("'abcabc'.find('b') gives 1: found", () => { + expect(OurCommon.findStr("abcabc", "b")).toStrictEqual(1); +}); + +test("'abcabc'.find('x') gives -1: not found", () => { + expect(OurCommon.findStr("abcabc", "x")).toStrictEqual(-1); +}); + +test("'abcabc'.find('') gives 0: empty sub", () => { + expect(OurCommon.findStr("abcabc", "")).toStrictEqual(0); +}); + +test("'ab'.find('abc') gives -1: sub longer than text", () => { + expect(OurCommon.findStr("ab", "abc")).toStrictEqual(-1); +}); + +test("'abcabc'.find('b', 2) gives 4: found after start", () => { + expect(OurCommon.findStr("abcabc", "b", 2)).toStrictEqual(4); +}); + +test("'abcabc'.find('b', 5) gives -1: found only before start", () => { + expect(OurCommon.findStr("abcabc", "b", 5)).toStrictEqual(-1); +}); + +test("'abcabc'.find('c', -1) gives 5: negative start counts from the end", () => { + expect(OurCommon.findStr("abcabc", "c", -1)).toStrictEqual(5); +}); + +test("'abcabc'.find('a', -3) gives 3: negative start finds the later occurrence", () => { + expect(OurCommon.findStr("abcabc", "a", -3)).toStrictEqual(3); +}); + +test("'abcabc'.find('a', -10) gives 0: negative start before the beginning is clamped", () => { + expect(OurCommon.findStr("abcabc", "a", -10)).toStrictEqual(0); +}); + +test("'abcabc'.find('', 6) gives 6: empty sub at the end", () => { + expect(OurCommon.findStr("abcabc", "", 6)).toStrictEqual(6); +}); + +test("'abcabc'.find('', 7) gives -1: empty sub beyond the end gives -1", () => { + expect(OurCommon.findStr("abcabc", "", 7)).toStrictEqual(-1); +}); + +test("'abcabc'.find('c', 10) gives -1: start beyond the end gives -1", () => { + expect(OurCommon.findStr("abcabc", "c", 10)).toStrictEqual(-1); +}); + +test("''.find('') gives 0: empty sub in empty text", () => { + expect(OurCommon.findStr("", "")).toStrictEqual(0); +}); + +test("''.find('', 1) gives -1: empty sub beyond the end of empty text gives -1", () => { + expect(OurCommon.findStr("", "", 1)).toStrictEqual(-1); +}); + +test("''.find('x') gives -1: not found in empty text", () => { + expect(OurCommon.findStr("", "x")).toStrictEqual(-1); +}); + +test("'é-'.find('-') gives 1: after a character of two UTF-8 bytes", () => { + expect(OurCommon.findStr("é-", "-")).toStrictEqual(1); +}); + +test("'😀-'.find('-') gives 1: after a character beyond the Basic Multilingual Plane", () => { + expect(OurCommon.findStr("😀-", "-")).toStrictEqual(1); +}); + +test("'a😀b'.find('😀') gives 1: character beyond the Basic Multilingual Plane", () => { + expect(OurCommon.findStr("a😀b", "😀")).toStrictEqual(1); +}); + +test("'😀a😀a'.find('a', 2) gives 3: start after a character beyond ASCII", () => { + expect(OurCommon.findStr("😀a😀a", "a", 2)).toStrictEqual(3); +}); + +test("'😀a😀a'.find('😀', -2) gives 2: negative start on characters beyond ASCII", () => { + expect(OurCommon.findStr("😀a😀a", "😀", -2)).toStrictEqual(2); +}); + +test("'é😀'.find('', 3) gives -1: start beyond the end of characters beyond ASCII", () => { + expect(OurCommon.findStr("é😀", "", 3)).toStrictEqual(-1); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.ts new file mode 100644 index 000000000..42a5f9de4 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/common.ts @@ -0,0 +1,380 @@ +/** + * Provide common functionality to be re-used across different tests + * such as reading of commonly-used environment variables. + */ + +import * as fs from "fs"; +import * as path from "path"; + +import * as OurTypes from "../src/types"; +import * as OurVerification from "../src/verification"; +import * as OurJsonization from "../src/jsonization"; +import { Path } from "../src/jsonization"; + +// NOTE (mristin): +// It is tedious to record manually all the expected error messages. Therefore we include this variable +// to steer the automatic recording. We intentionally inter-twine the recording code with the test code +// to keep them close to each other so that they are easier to maintain. +export const RECORD_MODE_ENVIRONMENT_VARIABLE_NAME = + "DUMMY_TEST_RECORD_MODE"; + +const RECORD_MODE_TEXT = + process.env[RECORD_MODE_ENVIRONMENT_VARIABLE_NAME]?.toLowerCase(); +export const RECORD_MODE: boolean = + RECORD_MODE_TEXT === "true" || RECORD_MODE_TEXT === "1" || RECORD_MODE_TEXT === "on"; + +export const TEST_DATA_DIR = process.env[ + "DUMMY_TEST_DATA_DIR" +]; +if (TEST_DATA_DIR === null || TEST_DATA_DIR === undefined) { + throw new Error( + "The path to the test data directory is missing in the environment: " + + "DUMMY_TEST_DATA_DIR" + ); +} +if (!fs.existsSync(TEST_DATA_DIR)) { + throw new Error( + "The path read from environment variable " + + "DUMMY_TEST_DATA_DIR does not exist: " + + TEST_DATA_DIR + ); +} +if (!fs.lstatSync(TEST_DATA_DIR).isDirectory()) { + throw new Error( + "The path read from environment variable " + + "DUMMY_TEST_DATA_DIR is not a directory: " + + TEST_DATA_DIR + ); +} + +/** + * Read a JSON value from a file. + * + * @param aPath - to the file + * @returns a JSON value read from the file + */ +export function readJsonFromFileSync(aPath: string): OurJsonization.JsonValue { + const text = fs.readFileSync(aPath, "utf-8"); + let jsonable: OurJsonization.JsonValue | null = null; + try { + jsonable = JSON.parse(text); + } catch (error) { + throw new Error(`Failed to parse JSON from: ${aPath}`); + } + + if (jsonable === null) { + throw new Error(`Unexpected null value as JSON from: ${aPath}`); + } + + return jsonable; +} + +/** + * Find the first instance beneath and including the `container` which satisfies + * `condition`. + * + * @param container - where to search + * @param condition - that needs to be fulfilled + * @throws an {@link Error} if no instance could be found which satisfies + * `condition` + */ +export function mustFind( + container: OurTypes.Class, + condition: (instance: OurTypes.Class) => boolean +) { + if (condition(container)) { + return container; + } + + for (const instance of container.descend()) { + if (condition(instance)) { + return instance; + } + } + + throw new Error("No instance could be found which satisfies the condition."); +} + +/** + * Assert that there are no verification errors in the `iterable`. + * + * @param errors - iterable of verification errors + * @param aPath - to the file specifying the instance + * @throws an {@link Error} with an informative message + */ +export function assertNoVerificationErrors( + errors: IterableIterator, + aPath: string +): void { + const errorArray = errors instanceof Array ? errors : Array.from(errors); + + if (errorArray.length !== 0) { + let message = + "Expected no errors when verifying the instance de-serialized " + + `from ${aPath}, but got ${errorArray.length} error(s):`; + + for (let i = 0; i < errorArray.length; i++) { + const error = errorArray[i]; + + message += `\n\nError ${i + 1}:\n${error.path}: ${error.message}`; + } + throw new Error(message); + } +} + +/** + * Assert that `errors` either correspond to the errors recorded to the disk, + * or re-record the errors, if {@link RECORD_MODE} is set. + * + * @param errors - iterable of verification errors on an instance + * @param aPath - to the instance + * @throws an {@link Error} if {@link RECORD_MODE} unset and the observed and + * recorded errors do not coincide + */ +export function assertExpectedOrRecordedVerificationErrors( + errors: IterableIterator, + aPath: string +): void { + const errorArray = errors instanceof Array ? errors : Array.from(errors); + + if (errorArray.length === 0) { + throw new Error( + "Expected at least one verification error when " + + `verifying ${path}, but got none` + ); + } + + const got = errorArray.map((error) => `${error.path}: ${error.message}`).join(";\n"); + + const errorsPath = aPath + ".errors"; + if (RECORD_MODE) { + fs.writeFileSync(errorsPath, got, "utf-8"); + } else { + if (!fs.existsSync(errorsPath)) { + throw new Error( + `The file with the recorded verification errors does not ` + + `exist: ${errorsPath}; you probably want to set the environment ` + + `variable ${RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = fs.readFileSync(errorsPath, "utf-8"); + if (expected !== got) { + throw new Error( + `Expected verification errors from ${path}:\n` + + `${expected}\n, but got:\n${got}` + ); + } + } +} + +/** + * Represent the `instance` as a human-readable line of an iteration trace. + * + * @param instance - to leave a mark in the trace + * @returns the mark in the trace + */ +export function traceMark(instance: OurTypes.Class): string { + return instance.constructor.name; +} + +/** + * Iterate over all the files beneath `directory` with the given `suffix` path. + * + * @param directory - to iterate through recursively + * @param suffix - expected suffix of the file name + */ +export function* findFilesBySuffixRecursively( + directory: string, + suffix: string +): IterableIterator { + for (const filename of fs.readdirSync(directory)) { + const pth = path.join(directory, filename); + const stat = fs.lstatSync(pth); + if (stat.isDirectory()) { + yield* findFilesBySuffixRecursively(pth, suffix); + } else { + if (filename.endsWith(suffix)) { + yield pth; + } + } + } +} + +/** + * Iterate over all the immediate subdirectories `directory`. + * + * @param directory - to iterate through + */ +export function* findImmediateSubdirectories( + directory: string +): IterableIterator { + for (const filename of fs.readdirSync(directory)) { + const pth = path.join(directory, filename); + const stat = fs.lstatSync(pth); + if (stat.isDirectory()) { + yield pth; + } + } +} + +/** + * Signal that two JSON-able structures are unequal. + */ +export class InequalityError { + /** + * Human-readable explanation of the error + */ + readonly message: string; + + /** + * Relative path to the erroneous value + */ + readonly path: OurJsonization.Path; + + constructor(message: string, path: OurJsonization.Path | null = null) { + this.message = message; + this.path = path ?? new Path(); + } +} + +/** + * Check that the `expected` JSON-able structure strictly equals `got` + * JSON-able structure. + * + * @param expected - JSON-able structure + * @param got - JSON-able structure + */ +export function checkJsonablesEqual( + expected: OurJsonization.JsonValue | null, + got: OurJsonization.JsonValue | null +): OurJsonization.DeserializationError | null { + if ( + expected === null || + typeof expected === "boolean" || + typeof expected === "number" || + typeof expected === "string" + ) { + if (expected !== got) { + if ( + got === null || + typeof got === "boolean" || + typeof got === "number" || + typeof got === "string" + ) { + return new InequalityError( + `Expected ${JSON.stringify(expected)}, ` + `but got ${JSON.stringify(got)}` + ); + } else { + return new InequalityError( + `Expected ${JSON.stringify(expected)}, ` + + `but got an instance of ${got.constructor.name}` + ); + } + } + } else if ( + typeof expected === "object" && + typeof expected[Symbol.iterator] === "function" + ) { + if (typeof got !== "object") { + return new InequalityError( + `Expected an iterable, ` + `but got ${JSON.stringify(got)}` + ); + } + + if (typeof got[Symbol.iterator] !== "function") { + return new InequalityError( + `Expected an iterable, ` + `but got an instance of ${got.constructor.name}` + ); + } + + const expectedIt = >expected[Symbol.iterator](); + + const gotIt = >got[Symbol.iterator](); + + let i = 0; + // eslint-disable-next-line no-constant-condition + while (true) { + const expectedResult = expectedIt.next(); + const gotResult = gotIt.next(); + + if (expectedResult.done && gotResult.done) { + break; + } + + if (expectedResult.done && !gotResult.done) { + return new InequalityError( + `Expected an iterable with ${i + 1} items, ` + + `but got an iterable with more items` + ); + } + + if (!expectedResult.done && gotResult.done) { + return new InequalityError( + `Expected an iterable with more than ${i + 1} item(s), ` + + `but got an iterable with only ${i + 1} item(s)` + ); + } + + const expectedItem = expectedResult.value; + const gotItem = expectedResult.value; + + const error = checkJsonablesEqual(expectedItem, gotItem); + if (error !== null) { + error.path.prepend( + new OurJsonization.IndexSegment(expected, i) + ); + return error; + } + + i++; + } + } else if (typeof expected === "object") { + const expectedKeys = new Set( + Object.keys(expected).filter((key) => + Object.prototype.hasOwnProperty.call(expected, key) + ) + ); + + const gotKeys = new Set( + Object.keys(expected).filter((key) => + Object.prototype.hasOwnProperty.call(got, key) + ) + ); + + for (const key of expectedKeys) { + if (!gotKeys.has(key)) { + return new InequalityError( + `Expected an object with key ${JSON.stringify(key)}, ` + + `but got an object without that key` + ); + } + } + + for (const key of gotKeys) { + if (!expectedKeys.has(key)) { + return new InequalityError( + `Expected an object without the key ${JSON.stringify(key)}, ` + + `but got an object with that key` + ); + } + } + + for (const key of expectedKeys) { + const expectedValue = expected[key]; + const gotValue = got[key]; + + const error = checkJsonablesEqual(expectedValue, gotValue); + if (error !== null) { + error.path.prepend( + new OurJsonization.PropertySegment(expected, key) + ); + return error; + } + } + } else { + throw new Error(`Unexpected expected value: ${expected}`); + } + + return null; +} diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonJsonization.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonJsonization.ts new file mode 100644 index 000000000..fb7ed37c2 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonJsonization.ts @@ -0,0 +1,272 @@ +/** + * Provide functions for loading generated examples of model instances. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as path from "path"; + +import * as OurTypes from "../src/types"; +import * as OurJsonization from "../src/jsonization"; + +import * as TestCommon from "./common"; + +/** + * Load a maximal example of {@link types.SomeItem} from + * the test data directory. + */ +export function loadMaximalSomeItem( +): OurTypes.SomeItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem", + "maximal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSomeItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SomeItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal example of {@link types.SomeItem} from + * the test data directory. + */ +export function loadMinimalSomeItem( +): OurTypes.SomeItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem", + "minimal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSomeItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SomeItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal example of {@link types.SpecialItem} from + * the test data directory. + */ +export function loadMaximalSpecialItem( +): OurTypes.SpecialItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem", + "maximal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.specialItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSpecialItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SpecialItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal example of {@link types.SpecialItem} from + * the test data directory. + */ +export function loadMinimalSpecialItem( +): OurTypes.SpecialItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem", + "minimal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.specialItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSpecialItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SpecialItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal example of {@link types.Container} from + * the test data directory. + */ +export function loadMaximalContainer( +): OurTypes.Container { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Container", + "maximal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.containerFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asContainer(instance); + if (casted === null) { + throw new Error( + `Expected instance of Container in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal example of {@link types.Container} from + * the test data directory. + */ +export function loadMinimalContainer( +): OurTypes.Container { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Container", + "minimal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.containerFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asContainer(instance); + if (casted === null) { + throw new Error( + `Expected instance of Container in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal example of {@link types.Wrapper} from + * the test data directory. + */ +export function loadMaximalWrapper( +): OurTypes.Wrapper { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper", + "maximal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.wrapperFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asWrapper(instance); + if (casted === null) { + throw new Error( + `Expected instance of Wrapper in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal example of {@link types.Wrapper} from + * the test data directory. + */ +export function loadMinimalWrapper( +): OurTypes.Wrapper { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper", + "minimal.json" + ); + + const jsonable = TestCommon.readJsonFromFileSync(aPath); + + const instanceOrError = OurJsonization.wrapperFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asWrapper(instance); + if (casted === null) { + throw new Error( + `Expected instance of Wrapper in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonXmlization.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonXmlization.ts new file mode 100644 index 000000000..3935e75ad --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/commonXmlization.ts @@ -0,0 +1,273 @@ +/** + * Provide functions for loading generated XML examples of model instances. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as fs from "fs"; +import * as path from "path"; + +import * as OurTypes from "../src/types"; +import * as OurXmlization from "../src/xmlization"; + +import * as TestCommon from "./common"; + +/** + * Load a maximal XML example of {@link types.SomeItem} + * from the test data directory. + */ +export function loadMaximalSomeItem( +): OurTypes.SomeItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "someItem", + "maximal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSomeItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SomeItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal XML example of {@link types.SomeItem} + * from the test data directory. + */ +export function loadMinimalSomeItem( +): OurTypes.SomeItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "someItem", + "minimal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSomeItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SomeItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal XML example of {@link types.SpecialItem} + * from the test data directory. + */ +export function loadMaximalSpecialItem( +): OurTypes.SpecialItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "specialItem", + "maximal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSpecialItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SpecialItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal XML example of {@link types.SpecialItem} + * from the test data directory. + */ +export function loadMinimalSpecialItem( +): OurTypes.SpecialItem { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "specialItem", + "minimal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSpecialItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SpecialItem in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal XML example of {@link types.Container} + * from the test data directory. + */ +export function loadMaximalContainer( +): OurTypes.Container { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "container", + "maximal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asContainer(instance); + if (casted === null) { + throw new Error( + `Expected instance of Container in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal XML example of {@link types.Container} + * from the test data directory. + */ +export function loadMinimalContainer( +): OurTypes.Container { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "container", + "minimal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asContainer(instance); + if (casted === null) { + throw new Error( + `Expected instance of Container in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a maximal XML example of {@link types.Wrapper} + * from the test data directory. + */ +export function loadMaximalWrapper( +): OurTypes.Wrapper { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "wrapper", + "maximal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asWrapper(instance); + if (casted === null) { + throw new Error( + `Expected instance of Wrapper in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +/** + * Load a minimal XML example of {@link types.Wrapper} + * from the test data directory. + */ +export function loadMinimalWrapper( +): OurTypes.Wrapper { + const aPath = path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "wrapper", + "minimal.xml" + ); + + const text = fs.readFileSync(aPath, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString( + text + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asWrapper(instance); + if (casted === null) { + throw new Error( + `Expected instance of Wrapper in ${aPath}, ` + + `but got: ${typeof instance}` + ); + } + return casted; +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.common.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.common.spec.ts new file mode 100644 index 000000000..27d3621cb --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.common.spec.ts @@ -0,0 +1,31 @@ +/** + * Execute all common JSONization loader helpers once. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as TestCommonJsonization from "./commonJsonization"; + +test("commonJsonization loaders for SomeItem", () => { + expect(TestCommonJsonization.loadMinimalSomeItem()).not.toBeNull(); + expect(TestCommonJsonization.loadMaximalSomeItem()).not.toBeNull(); +}); + +test("commonJsonization loaders for SpecialItem", () => { + expect(TestCommonJsonization.loadMinimalSpecialItem()).not.toBeNull(); + expect(TestCommonJsonization.loadMaximalSpecialItem()).not.toBeNull(); +}); + +test("commonJsonization loaders for Container", () => { + expect(TestCommonJsonization.loadMinimalContainer()).not.toBeNull(); + expect(TestCommonJsonization.loadMaximalContainer()).not.toBeNull(); +}); + +test("commonJsonization loaders for Wrapper", () => { + expect(TestCommonJsonization.loadMinimalWrapper()).not.toBeNull(); + expect(TestCommonJsonization.loadMaximalWrapper()).not.toBeNull(); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.concreteClasses.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.concreteClasses.spec.ts new file mode 100644 index 000000000..c3c9159fa --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.concreteClasses.spec.ts @@ -0,0 +1,764 @@ +/** + * Test JSON de/serialization of concrete classes. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as fs from "fs"; +import * as path from "path"; + +import * as OurJsonization from "../src/jsonization"; +import * as OurTypes from "../src/types"; +import * as OurVerification from "../src/verification"; + +import * as TestCommon from "./common"; + +/** + * Assert that the result of the chain JSON → de-serialize → object → serialize → JSON + * gives the input. + */ +function assertSerializeDeserializeEqualsOriginal( + originalJsonable: OurJsonization.JsonValue, + instance: OurTypes.Class, + aPath: string +): void { + let jsonable: OurJsonization.JsonValue | null = null; + try { + jsonable = OurJsonization.toJsonable(instance); + } catch (error) { + throw new Error( + "Expected no exception during JSON serialization " + + `of an instance of ${instance.constructor.name} from ${aPath}, ` + + `but got: ${error}` + ); + } + + const inequalityError = TestCommon.checkJsonablesEqual( + originalJsonable, + jsonable + ); + if (inequalityError !== null) { + throw new Error( + `The original JSON from ${aPath} is unequal the serialized JSON: ` + + `${inequalityError.path}: ${inequalityError.message}` + ); + } +} + +/** + * Assert that the deserialization error equals the expected golden one, + * or, if {@link common.RECORD_MODE} set, re-record the expected error. + * + * @param error - obtained error during the de-serialization + * @param aPath - to the JSON file which caused the de-serialization error + * @throws an {@link Error} if assertion fails + */ +function assertDeserializationErrorEqualsExpectedOrRecord( + error: OurJsonization.DeserializationError, + aPath: string +): void { + const errorPath = aPath + ".error"; + const got = `${error.path}: ${error.message}\n`; + + if (TestCommon.RECORD_MODE) { + fs.writeFileSync(errorPath, got, "utf-8"); + } else { + if (!fs.existsSync(errorPath)) { + throw new Error( + `The file with the recorded deserialization error does ` + + `not exist: ${errorPath}; you probably want to set ` + + `the environment variable ${TestCommon.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = + fs.readFileSync(errorPath, "utf-8") + .replace(/\r\n/g, "\n"); + if (expected !== got) { + throw new Error( + `Expected the error:\n${JSON.stringify(expected)}\n, ` + + `but got:\n${JSON.stringify(got)}\n` + + `when de-serializing from ${aPath}` + ); + } + } +} + +/** + * Assert that the obtained verification errors equal the expected verification errors, + * or, if {@link common.RECORD_MODE} set, re-record the expected errors. + * + * @param errors - obtained verification errors + * @param aPath - to the JSON file which caused the verification errors + * @throws an {@link Error} if assertion fails + */ +function assertVerificationErrorsEqualExpectedOrRecord( + errors: Array, + aPath: string +): void { + const errorsPath = aPath + ".errors"; + + const lines = new Array(); + for (const error of errors) { + lines.push(`${error.path}: ${error.message}`); + } + // NOTE (mristin): + // We add a new line for POSIX systems which prefer a new line + // at the end of the file. + lines.push(""); + const got = lines.join("\n"); + + if (TestCommon.RECORD_MODE) { + fs.writeFileSync(errorsPath, got, "utf-8"); + } else { + if (!fs.existsSync(errorsPath)) { + throw new Error( + `The file with the recorded verification errors ` + + `does not exist: ${errorsPath}; you probably want to set the environment ` + + `variable ${TestCommon.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = + fs.readFileSync(errorsPath, "utf-8") + .replace(/\r\n/g, "\n"); + if (expected !== got) { + throw new Error( + `Expected the error(s):\n${JSON.stringify(expected)}\n, ` + + `but got:\n${JSON.stringify(got)}\n` + + `when verifying ${aPath}` + ); + } + } +} + +test("SomeItem round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SomeItem" + ), + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + TestCommon.assertNoVerificationErrors(OurVerification.verify(instance), pth); + + assertSerializeDeserializeEqualsOriginal( + jsonable, + instance, + pth + ); + } +}); + +test("SomeItem deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) { + // NOTE (mristin): + // Unlike other SDKs, we can not be really sure what additional properties + // JavaScript might bring about. Therefore, we leave out the tests with + // the validation of additional properties. + if (path.basename(causeDir) == "UnexpectedAdditionalProperty") { + continue; + } + + const clsDir = path.join( + causeDir, + "SomeItem" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + if (instanceOrError.error === null) { + throw new Error(`Expected a de-serialization error for ${pth}, but got none`); + } + + assertDeserializationErrorEqualsExpectedOrRecord( + instanceOrError.error, + pth + ); + } + } +}); + +test("SomeItem verification fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "SomeItem" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + if (instanceOrError.error !== null) { + throw new Error( + `Expected no de-serialization error for ${pth}, ` + + `but got: ${instanceOrError.error.message}: ${instanceOrError.error.path}` + ); + } + + const instance = instanceOrError.mustValue(); + + const verificationErrors = Array.from(OurVerification.verify(instance)); + assertVerificationErrorsEqualExpectedOrRecord( + verificationErrors, + pth + ); + } + } +}); + +test("SpecialItem round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "SpecialItem" + ), + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.specialItemFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + TestCommon.assertNoVerificationErrors(OurVerification.verify(instance), pth); + + assertSerializeDeserializeEqualsOriginal( + jsonable, + instance, + pth + ); + } +}); + +test("SpecialItem deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) { + // NOTE (mristin): + // Unlike other SDKs, we can not be really sure what additional properties + // JavaScript might bring about. Therefore, we leave out the tests with + // the validation of additional properties. + if (path.basename(causeDir) == "UnexpectedAdditionalProperty") { + continue; + } + + const clsDir = path.join( + causeDir, + "SpecialItem" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.specialItemFromJsonable( + jsonable + ); + if (instanceOrError.error === null) { + throw new Error(`Expected a de-serialization error for ${pth}, but got none`); + } + + assertDeserializationErrorEqualsExpectedOrRecord( + instanceOrError.error, + pth + ); + } + } +}); + +test("SpecialItem verification fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "SpecialItem" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.specialItemFromJsonable( + jsonable + ); + if (instanceOrError.error !== null) { + throw new Error( + `Expected no de-serialization error for ${pth}, ` + + `but got: ${instanceOrError.error.message}: ${instanceOrError.error.path}` + ); + } + + const instance = instanceOrError.mustValue(); + + const verificationErrors = Array.from(OurVerification.verify(instance)); + assertVerificationErrorsEqualExpectedOrRecord( + verificationErrors, + pth + ); + } + } +}); + +test("Container round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Container" + ), + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.containerFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + TestCommon.assertNoVerificationErrors(OurVerification.verify(instance), pth); + + assertSerializeDeserializeEqualsOriginal( + jsonable, + instance, + pth + ); + } +}); + +test("Container deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) { + // NOTE (mristin): + // Unlike other SDKs, we can not be really sure what additional properties + // JavaScript might bring about. Therefore, we leave out the tests with + // the validation of additional properties. + if (path.basename(causeDir) == "UnexpectedAdditionalProperty") { + continue; + } + + const clsDir = path.join( + causeDir, + "Container" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.containerFromJsonable( + jsonable + ); + if (instanceOrError.error === null) { + throw new Error(`Expected a de-serialization error for ${pth}, but got none`); + } + + assertDeserializationErrorEqualsExpectedOrRecord( + instanceOrError.error, + pth + ); + } + } +}); + +test("Container verification fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "Container" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.containerFromJsonable( + jsonable + ); + if (instanceOrError.error !== null) { + throw new Error( + `Expected no de-serialization error for ${pth}, ` + + `but got: ${instanceOrError.error.message}: ${instanceOrError.error.path}` + ); + } + + const instance = instanceOrError.mustValue(); + + const verificationErrors = Array.from(OurVerification.verify(instance)); + assertVerificationErrorsEqualExpectedOrRecord( + verificationErrors, + pth + ); + } + } +}); + +test("Wrapper round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + "Wrapper" + ), + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.wrapperFromJsonable( + jsonable + ); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + TestCommon.assertNoVerificationErrors(OurVerification.verify(instance), pth); + + assertSerializeDeserializeEqualsOriginal( + jsonable, + instance, + pth + ); + } +}); + +test("Wrapper deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Unserializable" + ) + ) + ) { + // NOTE (mristin): + // Unlike other SDKs, we can not be really sure what additional properties + // JavaScript might bring about. Therefore, we leave out the tests with + // the validation of additional properties. + if (path.basename(causeDir) == "UnexpectedAdditionalProperty") { + continue; + } + + const clsDir = path.join( + causeDir, + "Wrapper" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.wrapperFromJsonable( + jsonable + ); + if (instanceOrError.error === null) { + throw new Error(`Expected a de-serialization error for ${pth}, but got none`); + } + + assertDeserializationErrorEqualsExpectedOrRecord( + instanceOrError.error, + pth + ); + } + } +}); + +test("Wrapper verification fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Unexpected", + "Invalid" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "Wrapper" + ); + if (!fs.existsSync(clsDir)) { + // NOTE (mristin): + // Some classes indeed lack the invalid examples. + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".json" + ) + ); + pths.sort(); + + for (const pth of pths) { + const jsonable = TestCommon.readJsonFromFileSync(pth); + + const instanceOrError = OurJsonization.wrapperFromJsonable( + jsonable + ); + if (instanceOrError.error !== null) { + throw new Error( + `Expected no de-serialization error for ${pth}, ` + + `but got: ${instanceOrError.error.message}: ${instanceOrError.error.path}` + ); + } + + const instance = instanceOrError.mustValue(); + + const verificationErrors = Array.from(OurVerification.verify(instance)); + assertVerificationErrorsEqualExpectedOrRecord( + verificationErrors, + pth + ); + } + } +}); + +/** + * Load the first recorded example of the `modelType`. + */ +function loadTheFirstExpected(modelType: string): OurJsonization.JsonValue { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Json", + "Expected", + modelType + ), + ".json" + ) + ); + pths.sort(); + + if (pths.length === 0) { + throw new Error( + `Expected at least one recorded example of ${modelType}, but got none` + ); + } + + return TestCommon.readJsonFromFileSync(pths[0]); +} + +test( + "Special_item serialization fails on an integer outside the range representable in JSON " + + "at .level", + () => { + for (const value of [9007199254740992, -9007199254740992]) { + const instance = OurJsonization.specialItemFromJsonable( + loadTheFirstExpected("SpecialItem") + ).mustValue(); + + instance.level = value; + + let caught: unknown = null; + try { + OurJsonization.toJsonable(instance); + } catch (error) { + caught = error; + } + + if (!(caught instanceof OurJsonization.SerializationError)) { + throw new Error( + `Expected a SerializationError, but got: ${caught}` + ); + } + + expect(caught.path).toStrictEqual(".level"); + } + } +); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.enums.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.enums.spec.ts new file mode 100644 index 000000000..4c4901aa1 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.enums.spec.ts @@ -0,0 +1,12 @@ +/** + * Test JSON de/serialization of enumeration literals. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurJsonization from "../src/jsonization"; +import * as OurTypes from "../src/types"; + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.interfaces.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.interfaces.spec.ts new file mode 100644 index 000000000..d48f514f0 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.interfaces.spec.ts @@ -0,0 +1,155 @@ +/** + * Test JSON de/serialization of interfaces. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurJsonization from "../src/jsonization"; +import * as TestCommon from "./common"; +import * as TestCommonJsonization from "./commonJsonization"; + +test( + "IItem round-trip " + + "starting from SomeItem OK", + () => { + const instance = TestCommonJsonization.loadMinimalSomeItem(); + + const jsonable = OurJsonization.toJsonable(instance); + + const anotherInstanceOrError = OurJsonization.itemFromJsonable( + jsonable + ); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const anotherJsonable = OurJsonization.toJsonable(anotherInstance); + + const inequalityError = TestCommon.checkJsonablesEqual( + jsonable, + anotherJsonable + ); + if (inequalityError !== null) { + throw new Error( + "The minimal example of IItem " + + "as an instance of SomeItem serialized " + + "to JSON, then de-serialized and serialized again does not match " + + `the first JSON: ${inequalityError.path}: ${inequalityError.message}` + ); + } +}); + +test( + "IItem round-trip " + + "starting from SpecialItem OK", + () => { + const instance = TestCommonJsonization.loadMinimalSpecialItem(); + + const jsonable = OurJsonization.toJsonable(instance); + + const anotherInstanceOrError = OurJsonization.itemFromJsonable( + jsonable + ); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const anotherJsonable = OurJsonization.toJsonable(anotherInstance); + + const inequalityError = TestCommon.checkJsonablesEqual( + jsonable, + anotherJsonable + ); + if (inequalityError !== null) { + throw new Error( + "The minimal example of IItem " + + "as an instance of SpecialItem serialized " + + "to JSON, then de-serialized and serialized again does not match " + + `the first JSON: ${inequalityError.path}: ${inequalityError.message}` + ); + } +}); + +test("IItem deserialization fail", () => { + const jsonable = "This is not a IItem."; + + const instanceOrError = OurJsonization.itemFromJsonable( + jsonable + ); + expect(instanceOrError.error.message).toStrictEqual( + "Expected a JSON object, but got: string" + ); +}); + +test( + "ISomeItem round-trip " + + "starting from SpecialItem OK", + () => { + const instance = TestCommonJsonization.loadMinimalSpecialItem(); + + const jsonable = OurJsonization.toJsonable(instance); + + const anotherInstanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const anotherJsonable = OurJsonization.toJsonable(anotherInstance); + + const inequalityError = TestCommon.checkJsonablesEqual( + jsonable, + anotherJsonable + ); + if (inequalityError !== null) { + throw new Error( + "The minimal example of ISomeItem " + + "as an instance of SpecialItem serialized " + + "to JSON, then de-serialized and serialized again does not match " + + `the first JSON: ${inequalityError.path}: ${inequalityError.message}` + ); + } +}); + +test( + "ISomeItem round-trip " + + "starting from SomeItem OK", + () => { + const instance = TestCommonJsonization.loadMinimalSomeItem(); + + const jsonable = OurJsonization.toJsonable(instance); + + const anotherInstanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const anotherJsonable = OurJsonization.toJsonable(anotherInstance); + + const inequalityError = TestCommon.checkJsonablesEqual( + jsonable, + anotherJsonable + ); + if (inequalityError !== null) { + throw new Error( + "The minimal example of ISomeItem " + + "as an instance of SomeItem serialized " + + "to JSON, then de-serialized and serialized again does not match " + + `the first JSON: ${inequalityError.path}: ${inequalityError.message}` + ); + } +}); + +test("ISomeItem deserialization fail", () => { + const jsonable = "This is not a ISomeItem."; + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + expect(instanceOrError.error.message).toStrictEqual( + "Expected a JSON object, but got: string" + ); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.negative.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.negative.spec.ts new file mode 100644 index 000000000..0f84adc23 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/jsonization.negative.spec.ts @@ -0,0 +1,77 @@ +/** + * Test failure modes in JSON parsing to exercise defensive branches. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as path from "path"; + +import * as OurJsonization from "../src/jsonization"; + +import * as TestCommon from "./common"; + +function readExpectedJson( + classJsonName: string, + fileName: "minimal.json" | "maximal.json" +): OurJsonization.JsonValue { + const pth = path.join(TestCommon.TEST_DATA_DIR, "Json", "Expected", classJsonName, fileName); + return TestCommon.readJsonFromFileSync(pth); +} + +function mustBeJsonObject(jsonable: OurJsonization.JsonValue): OurJsonization.JsonObject { + if ( + typeof jsonable !== "object" || + jsonable === null || + typeof jsonable[Symbol.iterator] === "function" + ) { + throw new Error(`Expected a JSON object, but got: ${JSON.stringify(jsonable)}`); + } + + return jsonable; +} + +test("SomeItem deserialization fails on non-object", () => { + const jsonable = "This is not a JSON object."; + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonable + ); + + expect(instanceOrError.error).not.toBeNull(); +}); + +test("SomeItem deserialization fails with missing required property", () => { + const jsonable = readExpectedJson( + "SomeItem", + "minimal.json" + ); + + const jsonObject = { ...mustBeJsonObject(jsonable) }; + delete jsonObject["name"]; + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonObject + ); + + expect(instanceOrError.error).not.toBeNull(); +}); + +test("SomeItem deserialization fails with property type mismatch", () => { + const jsonable = readExpectedJson( + "SomeItem", + "minimal.json" + ); + + const jsonObject = { ...mustBeJsonObject(jsonable) }; + jsonObject["name"] = { definitely: "unexpected-object" }; + + const instanceOrError = OurJsonization.someItemFromJsonable( + jsonObject + ); + + expect(instanceOrError.error).not.toBeNull(); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/stringification.enums.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/stringification.enums.spec.ts new file mode 100644 index 000000000..4f812f49c --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/stringification.enums.spec.ts @@ -0,0 +1,12 @@ +/** + * Test string de/serialization of enumeration literals. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurStringification from "../src/stringification"; +import * as OurTypes from "../src/types"; + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.casts.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.casts.spec.ts new file mode 100644 index 000000000..28a4f6e4d --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.casts.spec.ts @@ -0,0 +1,172 @@ +/** + * Test `as*` and `is*` functions. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "../src/types"; +import * as TestCommonJsonization from "./commonJsonization"; + +test("casts over an instance of SomeItem", () => { + const theSomeItem = + TestCommonJsonization.loadMinimalSomeItem(); + + expect( + OurTypes.isItem(theSomeItem) + ).toStrictEqual(true); + expect( + OurTypes.asItem(theSomeItem) + ).toStrictEqual(theSomeItem); + + expect( + OurTypes.isSomeItem(theSomeItem) + ).toStrictEqual(true); + expect( + OurTypes.asSomeItem(theSomeItem) + ).toStrictEqual(theSomeItem); + + expect( + OurTypes.isSpecialItem(theSomeItem) + ).toStrictEqual(false); + expect( + OurTypes.asSpecialItem(theSomeItem) + ).toBeNull(); + + expect( + OurTypes.isContainer(theSomeItem) + ).toStrictEqual(false); + expect( + OurTypes.asContainer(theSomeItem) + ).toBeNull(); + + expect( + OurTypes.isWrapper(theSomeItem) + ).toStrictEqual(false); + expect( + OurTypes.asWrapper(theSomeItem) + ).toBeNull(); +}); + +test("casts over an instance of SpecialItem", () => { + const theSpecialItem = + TestCommonJsonization.loadMinimalSpecialItem(); + + expect( + OurTypes.isItem(theSpecialItem) + ).toStrictEqual(true); + expect( + OurTypes.asItem(theSpecialItem) + ).toStrictEqual(theSpecialItem); + + expect( + OurTypes.isSomeItem(theSpecialItem) + ).toStrictEqual(true); + expect( + OurTypes.asSomeItem(theSpecialItem) + ).toStrictEqual(theSpecialItem); + + expect( + OurTypes.isSpecialItem(theSpecialItem) + ).toStrictEqual(true); + expect( + OurTypes.asSpecialItem(theSpecialItem) + ).toStrictEqual(theSpecialItem); + + expect( + OurTypes.isContainer(theSpecialItem) + ).toStrictEqual(false); + expect( + OurTypes.asContainer(theSpecialItem) + ).toBeNull(); + + expect( + OurTypes.isWrapper(theSpecialItem) + ).toStrictEqual(false); + expect( + OurTypes.asWrapper(theSpecialItem) + ).toBeNull(); +}); + +test("casts over an instance of Container", () => { + const theContainer = + TestCommonJsonization.loadMinimalContainer(); + + expect( + OurTypes.isItem(theContainer) + ).toStrictEqual(false); + expect( + OurTypes.asItem(theContainer) + ).toBeNull(); + + expect( + OurTypes.isSomeItem(theContainer) + ).toStrictEqual(false); + expect( + OurTypes.asSomeItem(theContainer) + ).toBeNull(); + + expect( + OurTypes.isSpecialItem(theContainer) + ).toStrictEqual(false); + expect( + OurTypes.asSpecialItem(theContainer) + ).toBeNull(); + + expect( + OurTypes.isContainer(theContainer) + ).toStrictEqual(true); + expect( + OurTypes.asContainer(theContainer) + ).toStrictEqual(theContainer); + + expect( + OurTypes.isWrapper(theContainer) + ).toStrictEqual(false); + expect( + OurTypes.asWrapper(theContainer) + ).toBeNull(); +}); + +test("casts over an instance of Wrapper", () => { + const theWrapper = + TestCommonJsonization.loadMinimalWrapper(); + + expect( + OurTypes.isItem(theWrapper) + ).toStrictEqual(false); + expect( + OurTypes.asItem(theWrapper) + ).toBeNull(); + + expect( + OurTypes.isSomeItem(theWrapper) + ).toStrictEqual(false); + expect( + OurTypes.asSomeItem(theWrapper) + ).toBeNull(); + + expect( + OurTypes.isSpecialItem(theWrapper) + ).toStrictEqual(false); + expect( + OurTypes.asSpecialItem(theWrapper) + ).toBeNull(); + + expect( + OurTypes.isContainer(theWrapper) + ).toStrictEqual(false); + expect( + OurTypes.asContainer(theWrapper) + ).toBeNull(); + + expect( + OurTypes.isWrapper(theWrapper) + ).toStrictEqual(true); + expect( + OurTypes.asWrapper(theWrapper) + ).toStrictEqual(theWrapper); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendAndPassThroughVisitor.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendAndPassThroughVisitor.spec.ts new file mode 100644 index 000000000..8ddeb1cc4 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendAndPassThroughVisitor.spec.ts @@ -0,0 +1,178 @@ +/** + * Test `descend*` functions and `PassThroughVisitor`. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as path from "path"; +import * as fs from "fs"; + +import * as OurTypes from "../src/types"; +import * as TestCommon from "./common"; +import * as TestCommonJsonization from "./commonJsonization"; + +/** + * Record a human-readable trace of a descent over an object tree. + */ +class TracingVisitor extends OurTypes.PassThroughVisitor { + readonly log = new Array(); + + visit(that: OurTypes.Class) { + this.log.push(TestCommon.traceMark(that)); + super.visit(that); + } +} + +/** + * Expect that the trace from {@link types.Class.descend} and + * {@link TracingVisitor} are equal. + * + * @param instance - to be descended through + * @throws an {@link Error} if the two traces are not equal + */ +function expectDescendAndPassThroughVisitorSame( + instance: OurTypes.Class +): void { + const traceFromDescend = new Array(); + for (const subInstance of instance.descend()) { + traceFromDescend.push(TestCommon.traceMark(subInstance)); + } + + const visitor = new TracingVisitor(); + visitor.visit(instance); + const traceFromVisitor = visitor.log; + + expect(traceFromVisitor.length).toBeGreaterThan(0); + expect(TestCommon.traceMark(instance)).toStrictEqual(traceFromVisitor[0]); + + traceFromVisitor.shift(); + + expect(traceFromVisitor).toStrictEqual(traceFromDescend); +} + +/** + * Compare the trace against the golden one from the test data, + * or re-record the trace if {@link common.RECORD_MODE}. + * + * @param instance - to be traced + * @param expectedPath - path to the golden trace + */ +function compareOrRecordTrace( + instance: OurTypes.Class, + expectedPath: string +) { + const lines = new Array(); + for (const descendant of instance.descend()) { + lines.push(TestCommon.traceMark(descendant)); + } + // NOTE (mristin): + // We add a new line for POSIX systems which prefer a new line + // at the end of the file. + lines.push(""); + const got = lines.join("\n"); + + if (TestCommon.RECORD_MODE) { + const parent = path.dirname(expectedPath); + if (!fs.existsSync(parent)) { + fs.mkdirSync(parent, {recursive: true}); + } + fs.writeFileSync(expectedPath, got, "utf-8"); + } else { + if (!fs.existsSync(expectedPath)) { + throw new Error( + `The file with the recorded trace does not exist: ${expectedPath}; ` + + `you probably want to set the environment ` + + `variable ${TestCommon.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = + fs.readFileSync(expectedPath, "utf-8") + .replace(/\r\n/g, "\n"); + expect(got).toStrictEqual(expected); + } +} + +test("descend of SomeItem", () => { + const instance = TestCommonJsonization.loadMaximalSomeItem(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descend", + "SomeItem", + "maximal.json.trace" + ) + ); +}); + +test("descend against PassThroughVisitor", () => { + const instance = TestCommonJsonization.loadMaximalSomeItem(); + + expectDescendAndPassThroughVisitorSame(instance); +}); + +test("descend of SpecialItem", () => { + const instance = TestCommonJsonization.loadMaximalSpecialItem(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descend", + "SpecialItem", + "maximal.json.trace" + ) + ); +}); + +test("descend against PassThroughVisitor", () => { + const instance = TestCommonJsonization.loadMaximalSpecialItem(); + + expectDescendAndPassThroughVisitorSame(instance); +}); + +test("descend of Container", () => { + const instance = TestCommonJsonization.loadMaximalContainer(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descend", + "Container", + "maximal.json.trace" + ) + ); +}); + +test("descend against PassThroughVisitor", () => { + const instance = TestCommonJsonization.loadMaximalContainer(); + + expectDescendAndPassThroughVisitorSame(instance); +}); + +test("descend of Wrapper", () => { + const instance = TestCommonJsonization.loadMaximalWrapper(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descend", + "Wrapper", + "maximal.json.trace" + ) + ); +}); + +test("descend against PassThroughVisitor", () => { + const instance = TestCommonJsonization.loadMaximalWrapper(); + + expectDescendAndPassThroughVisitorSame(instance); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendOnce.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendOnce.spec.ts new file mode 100644 index 000000000..e5e3a3211 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.descendOnce.spec.ts @@ -0,0 +1,115 @@ +/** + * Test `descendOnce*` functions. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as path from "path"; +import * as fs from "fs"; + +import * as OurTypes from "../src/types"; +import * as TestCommon from "./common"; +import * as TestCommonJsonization from "./commonJsonization"; + +/** + * Compare the trace against the golden one from the test data, + * or re-record the trace if {@link common.RECORD_MODE}. + * + * @param instance - to be traced + * @param expectedPath - path to the golden trace + */ +function compareOrRecordTrace( + instance: OurTypes.Class, + expectedPath: string +) { + const lines = new Array(); + for (const descendant of instance.descendOnce()) { + lines.push(TestCommon.traceMark(descendant)); + } + // NOTE (mristin): + // We add a new line for POSIX systems which prefer a new line + // at the end of the file. + lines.push(""); + const got = lines.join("\n"); + + if (TestCommon.RECORD_MODE) { + const parent = path.dirname(expectedPath); + if (!fs.existsSync(parent)) { + fs.mkdirSync(parent, {recursive: true}); + } + fs.writeFileSync(expectedPath, got, "utf-8"); + } else { + if (!fs.existsSync(expectedPath)) { + throw new Error( + `The file with the recorded trace does not exist: ${expectedPath}; ` + + `you probably want to set the environment ` + + `variable ${TestCommon.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = + fs.readFileSync(expectedPath, "utf-8") + .replace(/\r\n/g, "\n"); + expect(got).toStrictEqual(expected); + } +} + +test("descendOnce of SomeItem", () => { + const instance = TestCommonJsonization.loadMaximalSomeItem(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descendOnce", + "SomeItem", + "maximal.json.trace" + ) + ); +}); + +test("descendOnce of SpecialItem", () => { + const instance = TestCommonJsonization.loadMaximalSpecialItem(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descendOnce", + "SpecialItem", + "maximal.json.trace" + ) + ); +}); + +test("descendOnce of Container", () => { + const instance = TestCommonJsonization.loadMaximalContainer(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descendOnce", + "Container", + "maximal.json.trace" + ) + ); +}); + +test("descendOnce of Wrapper", () => { + const instance = TestCommonJsonization.loadMaximalWrapper(); + + compareOrRecordTrace( + instance, + path.join( + TestCommon.TEST_DATA_DIR, + "descendOnce", + "Wrapper", + "maximal.json.trace" + ) + ); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.modelType.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.modelType.spec.ts new file mode 100644 index 000000000..bdf3f4c51 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.modelType.spec.ts @@ -0,0 +1,148 @@ +/** + * Test `modelType()` methods. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "../src/types"; +import * as OurStringification from "../src/stringification"; +import * as TestCommonJsonization from "./commonJsonization"; + +test("model type of SomeItem", () => { + const instance = TestCommonJsonization.loadMinimalSomeItem(); + + expect(instance.modelType()).toStrictEqual( + OurTypes.ModelType.SomeItem + ); +}); + +test("model type from string of SomeItem", () => { + const text = "SomeItem"; + const literal = OurStringification.modelTypeFromString( + text + ); + + expect(literal).toStrictEqual( + OurTypes.ModelType.SomeItem + ); +}); + +test("model type to string of SomeItem", () => { + const text = OurStringification.mustModelTypeToString( + OurTypes.ModelType.SomeItem + ); + + expect(text).toStrictEqual( + "SomeItem" + ); +}); + +test("model type of SpecialItem", () => { + const instance = TestCommonJsonization.loadMinimalSpecialItem(); + + expect(instance.modelType()).toStrictEqual( + OurTypes.ModelType.SpecialItem + ); +}); + +test("model type from string of SpecialItem", () => { + const text = "SpecialItem"; + const literal = OurStringification.modelTypeFromString( + text + ); + + expect(literal).toStrictEqual( + OurTypes.ModelType.SpecialItem + ); +}); + +test("model type to string of SpecialItem", () => { + const text = OurStringification.mustModelTypeToString( + OurTypes.ModelType.SpecialItem + ); + + expect(text).toStrictEqual( + "SpecialItem" + ); +}); + +test("model type of Container", () => { + const instance = TestCommonJsonization.loadMinimalContainer(); + + expect(instance.modelType()).toStrictEqual( + OurTypes.ModelType.Container + ); +}); + +test("model type from string of Container", () => { + const text = "Container"; + const literal = OurStringification.modelTypeFromString( + text + ); + + expect(literal).toStrictEqual( + OurTypes.ModelType.Container + ); +}); + +test("model type to string of Container", () => { + const text = OurStringification.mustModelTypeToString( + OurTypes.ModelType.Container + ); + + expect(text).toStrictEqual( + "Container" + ); +}); + +test("model type of Wrapper", () => { + const instance = TestCommonJsonization.loadMinimalWrapper(); + + expect(instance.modelType()).toStrictEqual( + OurTypes.ModelType.Wrapper + ); +}); + +test("model type from string of Wrapper", () => { + const text = "Wrapper"; + const literal = OurStringification.modelTypeFromString( + text + ); + + expect(literal).toStrictEqual( + OurTypes.ModelType.Wrapper + ); +}); + +test("model type to string of Wrapper", () => { + const text = OurStringification.mustModelTypeToString( + OurTypes.ModelType.Wrapper + ); + + expect(text).toStrictEqual( + "Wrapper" + ); +}); + +test("model type from invalid string", () => { + const text = "This is definitely not a valid model type."; + const literal = OurStringification.modelTypeFromString( + text + ); + + expect(literal).toBeNull(); +}); + +test("invalid model type to string", () => { + // The number 9007199254740991 is the maximum safe integer. + const literal = 9007199254740991; + const text = OurStringification.modelTypeToString( + literal + ); + + expect(text).toBeNull(); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overEnum.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overEnum.spec.ts new file mode 100644 index 000000000..3727b3cdc --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overEnum.spec.ts @@ -0,0 +1,11 @@ +/** + * Test `over{Enum}` functions. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "../src/types"; + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overXOrEmpty.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overXOrEmpty.spec.ts new file mode 100644 index 000000000..87bdaebab --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.overXOrEmpty.spec.ts @@ -0,0 +1,37 @@ +/** + * Test `over*OrEmpty` methods. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as TestCommonJsonization from "./commonJsonization"; + +test("Container.overSpecialItemsOrEmpty on maximal", () => { + const instance = TestCommonJsonization.loadMaximalContainer(); + + let count = 0; + for (const _ of instance.overSpecialItemsOrEmpty()) { + count++; + } + + expect(count).toStrictEqual( + instance.specialItems?.length ?? 0, + ); +}); + +test("Container.overSpecialItemsOrEmpty on minimal", () => { + const instance = TestCommonJsonization.loadMinimalContainer(); + + let count = 0; + for (const _ of instance.overSpecialItemsOrEmpty()) { + count++; + } + + expect(count).toStrictEqual( + instance.specialItems?.length ?? 0, + ); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.typeMatches.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.typeMatches.spec.ts new file mode 100644 index 000000000..15b725679 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.typeMatches.spec.ts @@ -0,0 +1,140 @@ +/** + * Test `typesMatch`. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "../src/types"; +import * as TestCommonJsonization from "./commonJsonization"; + +const THE_SOME_ITEM = TestCommonJsonization.loadMinimalSomeItem(); + +const THE_SPECIAL_ITEM = TestCommonJsonization.loadMinimalSpecialItem(); + +const THE_CONTAINER = TestCommonJsonization.loadMinimalContainer(); + +const THE_WRAPPER = TestCommonJsonization.loadMinimalWrapper(); + +test("type matches for SomeItem", () => { + expect( + OurTypes.typesMatch( + THE_SOME_ITEM, + THE_SOME_ITEM + ) + ).toStrictEqual(true); + + expect( + OurTypes.typesMatch( + THE_SOME_ITEM, + THE_SPECIAL_ITEM + ) + ).toStrictEqual(true); + + expect( + OurTypes.typesMatch( + THE_SOME_ITEM, + THE_CONTAINER + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_SOME_ITEM, + THE_WRAPPER + ) + ).toStrictEqual(false); +}); + +test("type matches for SpecialItem", () => { + expect( + OurTypes.typesMatch( + THE_SPECIAL_ITEM, + THE_SOME_ITEM + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_SPECIAL_ITEM, + THE_SPECIAL_ITEM + ) + ).toStrictEqual(true); + + expect( + OurTypes.typesMatch( + THE_SPECIAL_ITEM, + THE_CONTAINER + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_SPECIAL_ITEM, + THE_WRAPPER + ) + ).toStrictEqual(false); +}); + +test("type matches for Container", () => { + expect( + OurTypes.typesMatch( + THE_CONTAINER, + THE_SOME_ITEM + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_CONTAINER, + THE_SPECIAL_ITEM + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_CONTAINER, + THE_CONTAINER + ) + ).toStrictEqual(true); + + expect( + OurTypes.typesMatch( + THE_CONTAINER, + THE_WRAPPER + ) + ).toStrictEqual(false); +}); + +test("type matches for Wrapper", () => { + expect( + OurTypes.typesMatch( + THE_WRAPPER, + THE_SOME_ITEM + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_WRAPPER, + THE_SPECIAL_ITEM + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_WRAPPER, + THE_CONTAINER + ) + ).toStrictEqual(false); + + expect( + OurTypes.typesMatch( + THE_WRAPPER, + THE_WRAPPER + ) + ).toStrictEqual(true); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.xOrDefault.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.xOrDefault.spec.ts new file mode 100644 index 000000000..3253e6f79 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/types.xOrDefault.spec.ts @@ -0,0 +1,88 @@ +/** + * Test `*OrDefault` functions. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as path from "path"; +import * as fs from "fs"; + +import * as OurStringification from "../src/stringification"; +import * as OurTypes from "../src/types"; +import * as TestCommon from "./common"; +import * as TestCommonJsonization from "./commonJsonization"; + +/** + * Represent explicitly a literal of an enumeration. + */ +class EnumerationLiteral { + constructor(public enumerationName: string, public literalName) { + // Intentionally empty. + } + + toString(): string { + return `${this.enumerationName}.${this.literalName}`; + } +} + +/** + * Represent a value such that we can immediately check whether it is the default value + * or the set one. + * + * @remark + * We compare it against the recorded golden file, if not {@link common.RECORD_MODE}. + * Otherwise, when {@link common.RECORD_MODE} is set, we re-record the golden file. + * + * @param value - to be represented + * @param expectedPath - to the golden file + */ +function compareOrRecordValue( + value: boolean | number | string | null | EnumerationLiteral | OurTypes.Class, + expectedPath: string +): void { + let got = ""; + if ( + typeof value === "boolean" + || typeof value === "number" + || typeof value === "string" + || value === null + ) { + got = JSON.stringify(value); + } else if (value instanceof EnumerationLiteral) { + got = value.toString(); + } else if (value instanceof OurTypes.Class) { + got = TestCommon.traceMark(value); + } else { + throw new Error(`We do not know how to represent the value ${value}`); + } + + // NOTE (mristin): + // We add a new line for POSIX systems which prefer a new line + // at the end of the file. + got += "\n"; + + if (TestCommon.RECORD_MODE) { + const parent = path.dirname(expectedPath); + if (!fs.existsSync(parent)) { + fs.mkdirSync(parent, {recursive: true}); + } + fs.writeFileSync(expectedPath, got, "utf-8"); + } else { + if (!fs.existsSync(expectedPath)) { + throw new Error( + `The file with the recorded value does not exist: ${expectedPath}; ` + + `you probably want to set the environment ` + + `variable ${TestCommon.RECORD_MODE_ENVIRONMENT_VARIABLE_NAME}?` + ); + } + + const expected = + fs.readFileSync(expectedPath, "utf-8") + .replace(/\r\n/g, "\n"); + expect(got).toStrictEqual(expected); + } +} + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/verification.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/verification.spec.ts new file mode 100644 index 000000000..5ee1ece36 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/verification.spec.ts @@ -0,0 +1,49 @@ +/** + * Test verification helper classes and string-based verification functions. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurTypes from "../src/types"; +import * as OurVerification from "../src/verification"; + +test("verification path and segments format", () => { + const path = new OurVerification.Path(); + + path.prepend( + new OurVerification.IndexSegment( + >[], + 2 + ) + ); + path.prepend( + new OurVerification.PropertySegment( + {}, + "something" + ) + ); + + expect(path.toString()).toStrictEqual(".something[2]"); +}); + +test("verification error stores provided path", () => { + const path = new OurVerification.Path(); + path.prepend( + new OurVerification.PropertySegment( + {}, + "idShort" + ) + ); + + const error = new OurVerification.VerificationError( + "Some verification error", + path + ); + + expect(error.message).toStrictEqual("Some verification error"); + expect(error.path.toString()).toStrictEqual(".idShort"); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.basic.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.basic.spec.ts new file mode 100644 index 000000000..18592ec9d --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.basic.spec.ts @@ -0,0 +1,68 @@ +/** + * Test basic XMLization helper classes and malformed XML inputs. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurXmlization from "../src/xmlization"; + +test("xmlization path renders as a relative XPath", () => { + const path = new OurXmlization.Path(); + + path.prepend(new OurXmlization.IndexSegment(3)); + path.prepend(new OurXmlization.ElementSegment("something")); + + expect(path.toString()).toStrictEqual("something/*[4]"); +}); + +test("xmlization key segment renders as a predicate on the name", () => { + const path = new OurXmlization.Path(); + + path.prepend(new OurXmlization.ElementSegment("value")); + path.prepend(new OurXmlization.KeySegment("a \"tricky\" ")); + path.prepend(new OurXmlization.ElementSegment("struct")); + + expect(path.toString()).toStrictEqual( + "struct/member[name=\"a "tricky" <key>\"]/value" + ); +}); + +test("xmlization errors default to empty path", () => { + const deserializationError = new OurXmlization.DeserializationError("broken XML"); + expect(deserializationError.message).toStrictEqual("broken XML"); + expect(deserializationError.path.toString()).toStrictEqual(""); + + const serializationError = new OurXmlization.SerializationError("broken object graph"); + expect(serializationError.message).toStrictEqual("broken object graph"); + expect(serializationError.path).toStrictEqual(""); +}); + +test("xmlization serialization path renders as an access expression", () => { + const error = new OurXmlization.SerializationError("broken object graph"); + + error.prependKey("a b"); + error.prependIndex(3); + error.prependProperty("something"); + + expect(error.path).toStrictEqual('.something[3]["a b"]'); +}); + +test("xmlization fails on malformed XML", () => { + const malformedXml = ""; + const instanceOrError = OurXmlization.fromXmlString(malformedXml); + expect(instanceOrError.error).not.toBeNull(); +}); + +test("xmlization fails on empty XML", () => { + const instanceOrError = OurXmlization.fromXmlString(""); + expect(instanceOrError.error).not.toBeNull(); +}); + +test("xmlization fails on non-XML text", () => { + const instanceOrError = OurXmlization.fromXmlString("Definitely not XML"); + expect(instanceOrError.error).not.toBeNull(); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.common.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.common.spec.ts new file mode 100644 index 000000000..25dc04250 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.common.spec.ts @@ -0,0 +1,31 @@ +/** + * Execute all common XMLization loader helpers once. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as TestCommonXmlization from "./commonXmlization"; + +test("commonXmlization loaders for SomeItem", () => { + expect(TestCommonXmlization.loadMinimalSomeItem()).not.toBeNull(); + expect(TestCommonXmlization.loadMaximalSomeItem()).not.toBeNull(); +}); + +test("commonXmlization loaders for SpecialItem", () => { + expect(TestCommonXmlization.loadMinimalSpecialItem()).not.toBeNull(); + expect(TestCommonXmlization.loadMaximalSpecialItem()).not.toBeNull(); +}); + +test("commonXmlization loaders for Container", () => { + expect(TestCommonXmlization.loadMinimalContainer()).not.toBeNull(); + expect(TestCommonXmlization.loadMaximalContainer()).not.toBeNull(); +}); + +test("commonXmlization loaders for Wrapper", () => { + expect(TestCommonXmlization.loadMinimalWrapper()).not.toBeNull(); + expect(TestCommonXmlization.loadMaximalWrapper()).not.toBeNull(); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.concreteClasses.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.concreteClasses.spec.ts new file mode 100644 index 000000000..5435fad54 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.concreteClasses.spec.ts @@ -0,0 +1,306 @@ +/** + * Test XML de/serialization of concrete classes. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as fs from "fs"; +import * as path from "path"; + +import * as OurXmlization from "../src/xmlization"; +import * as OurTypes from "../src/types"; +import * as OurVerification from "../src/verification"; + +import * as TestCommon from "./common"; + +test("SomeItem XML round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "someItem" + ), + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSomeItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SomeItem in ${pth}, ` + + `but got: ${typeof instance}` + ); + } + + TestCommon.assertNoVerificationErrors(OurVerification.verify(casted), pth); + + const roundTripText = OurXmlization.toXmlString(casted); + expect(roundTripText.length).toBeGreaterThan(0); + } +}); + +test("SomeItem XML deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "someItem" + ); + if (!fs.existsSync(clsDir)) { + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).not.toBeNull(); + } + } +}); + +test("SpecialItem XML round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "specialItem" + ), + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asSpecialItem(instance); + if (casted === null) { + throw new Error( + `Expected instance of SpecialItem in ${pth}, ` + + `but got: ${typeof instance}` + ); + } + + TestCommon.assertNoVerificationErrors(OurVerification.verify(casted), pth); + + const roundTripText = OurXmlization.toXmlString(casted); + expect(roundTripText.length).toBeGreaterThan(0); + } +}); + +test("SpecialItem XML deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "specialItem" + ); + if (!fs.existsSync(clsDir)) { + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).not.toBeNull(); + } + } +}); + +test("Container XML round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "container" + ), + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asContainer(instance); + if (casted === null) { + throw new Error( + `Expected instance of Container in ${pth}, ` + + `but got: ${typeof instance}` + ); + } + + TestCommon.assertNoVerificationErrors(OurVerification.verify(casted), pth); + + const roundTripText = OurXmlization.toXmlString(casted); + expect(roundTripText.length).toBeGreaterThan(0); + } +}); + +test("Container XML deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "container" + ); + if (!fs.existsSync(clsDir)) { + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).not.toBeNull(); + } + } +}); + +test("Wrapper XML round-trip OK", () => { + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Expected", + "wrapper" + ), + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).toBeNull(); + const instance = instanceOrError.mustValue(); + + const casted = OurTypes.asWrapper(instance); + if (casted === null) { + throw new Error( + `Expected instance of Wrapper in ${pth}, ` + + `but got: ${typeof instance}` + ); + } + + TestCommon.assertNoVerificationErrors(OurVerification.verify(casted), pth); + + const roundTripText = OurXmlization.toXmlString(casted); + expect(roundTripText.length).toBeGreaterThan(0); + } +}); + +test("Wrapper XML deserialization fail", () => { + for ( + const causeDir of + TestCommon.findImmediateSubdirectories( + path.join( + TestCommon.TEST_DATA_DIR, + "Xml", + "Unexpected", + "Unserializable" + ) + ) + ) { + const clsDir = path.join( + causeDir, + "wrapper" + ); + if (!fs.existsSync(clsDir)) { + continue; + } + + const pths = Array.from( + TestCommon.findFilesBySuffixRecursively( + clsDir, + ".xml" + ) + ); + pths.sort(); + + for (const pth of pths) { + const text = fs.readFileSync(pth, "utf-8"); + const instanceOrError = OurXmlization.fromXmlString(text); + expect(instanceOrError.error).not.toBeNull(); + } + } +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.enums.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.enums.spec.ts new file mode 100644 index 000000000..db7bcb130 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.enums.spec.ts @@ -0,0 +1,14 @@ +/** + * Test XML de/serialization of enumeration literals. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurXmlization from "../src/xmlization"; +import * as OurTypes from "../src/types"; + +import * as TestCommonXmlization from "./commonXmlization"; + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.interfaces.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.interfaces.spec.ts new file mode 100644 index 000000000..8eadbda48 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.interfaces.spec.ts @@ -0,0 +1,166 @@ +/** + * Test XML de/serialization of interfaces. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as OurXmlization from "../src/xmlization"; +import * as OurTypes from "../src/types"; + +import * as TestCommonXmlization from "./commonXmlization"; + +test( + "IItem XML round-trip " + + "starting from SomeItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSomeItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.fromXmlString(xmlText); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const asInterface = OurTypes.asItem(anotherInstance); + expect(asInterface).not.toBeNull(); + } +); + +test( + "IItem XML round-trip via " + + "itemFromXmlString starting from SomeItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSomeItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.itemFromXmlString( + xmlText + ); + expect(anotherInstanceOrError.error).toBeNull(); + } +); + +test( + "IItem XML round-trip " + + "starting from SpecialItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSpecialItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.fromXmlString(xmlText); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const asInterface = OurTypes.asItem(anotherInstance); + expect(asInterface).not.toBeNull(); + } +); + +test( + "IItem XML round-trip via " + + "itemFromXmlString starting from SpecialItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSpecialItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.itemFromXmlString( + xmlText + ); + expect(anotherInstanceOrError.error).toBeNull(); + } +); + +test("IItem XML deserialization fail", () => { + const instanceOrError = OurXmlization.fromXmlString("This is not XML."); + expect(instanceOrError.error).not.toBeNull(); +}); + +test( + "IItem XML deserialization fail via " + + "itemFromXmlString", + () => { + const instanceOrError = OurXmlization.itemFromXmlString( + "This is not XML." + ); + expect(instanceOrError.error).not.toBeNull(); + } +); + +test( + "ISomeItem XML round-trip " + + "starting from SpecialItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSpecialItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.fromXmlString(xmlText); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const asInterface = OurTypes.asSomeItem(anotherInstance); + expect(asInterface).not.toBeNull(); + } +); + +test( + "ISomeItem XML round-trip via " + + "someItemFromXmlString starting from SpecialItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSpecialItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.someItemFromXmlString( + xmlText + ); + expect(anotherInstanceOrError.error).toBeNull(); + } +); + +test( + "ISomeItem XML round-trip " + + "starting from SomeItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSomeItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.fromXmlString(xmlText); + expect(anotherInstanceOrError.error).toBeNull(); + const anotherInstance = anotherInstanceOrError.mustValue(); + + const asInterface = OurTypes.asSomeItem(anotherInstance); + expect(asInterface).not.toBeNull(); + } +); + +test( + "ISomeItem XML round-trip via " + + "someItemFromXmlString starting from SomeItem OK", + () => { + const instance = TestCommonXmlization.loadMinimalSomeItem(); + + const xmlText = OurXmlization.toXmlString(instance); + const anotherInstanceOrError = OurXmlization.someItemFromXmlString( + xmlText + ); + expect(anotherInstanceOrError.error).toBeNull(); + } +); + +test("ISomeItem XML deserialization fail", () => { + const instanceOrError = OurXmlization.fromXmlString("This is not XML."); + expect(instanceOrError.error).not.toBeNull(); +}); + +test( + "ISomeItem XML deserialization fail via " + + "someItemFromXmlString", + () => { + const instanceOrError = OurXmlization.someItemFromXmlString( + "This is not XML." + ); + expect(instanceOrError.error).not.toBeNull(); + } +); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.negative.spec.ts b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.negative.spec.ts new file mode 100644 index 000000000..eb108073a --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/expected_output/test/xmlization.negative.spec.ts @@ -0,0 +1,133 @@ +/** + * Test failure modes in XML parsing to exercise defensive branches. + */ + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. + +import * as fs from "fs"; +import * as path from "path"; + +// NOTE (mristin): +// Initially, we used string operations to manipulate the XML files, but that lead +// to maintainability problems. For example, we suddenly had to take care of edge +// cases such as self-closing elements (e.g., ``). Instead, we decided +// to use a proper library. There is a DOMParser in XMLSerializer provided in +// the browser, but node.js does not ship with them, unless we explicitly add "dom". +// +// In the end, we decided to use an external library as development dependency since +// all the other approaches (gymnastics with jest, tsconfig and tsconfig.test) turned +// out to be much more complicated. +import { DOMParser, XMLSerializer } from "@xmldom/xmldom"; +import type { Document } from "@xmldom/xmldom"; + +import * as OurXmlization from "../src/xmlization"; + +import * as TestCommon from "./common"; + +function parseXml(xmlText: string): [Document, null] | [null, string] { + let errorMessage: string | null = null; + + const document = new DOMParser({ + errorHandler: (level: string, msg: string) => { + if (level === "error" || level === "fatalError") { + errorMessage = msg; + } + } + }).parseFromString(xmlText, "application/xml"); + + return errorMessage !== null ? [null, errorMessage] : [document, null]; +} + +function expectDeserializationError(xmlText: string): void { + const instanceOrError = OurXmlization.fromXmlString(xmlText); + expect(instanceOrError.error).not.toBeNull(); +} + +test("XML namespace mismatch fails", () => { + const pth = path.join( + TestCommon.TEST_DATA_DIR, "Xml", "Expected", "someItem", "minimal.xml" + ); + const text = fs.readFileSync(pth, "utf-8"); + const brokenText = text.replace( + /xmlns="[^"]*"/, + 'xmlns="urn:aas-core-codegen:broken"' + ); + + expect(brokenText).not.toStrictEqual(text); + expectDeserializationError(brokenText); +}); + +test("XML wrong root closing element fails", () => { + const pth = path.join( + TestCommon.TEST_DATA_DIR, "Xml", "Expected", "someItem", "minimal.xml" + ); + const text = fs.readFileSync(pth, "utf-8"); + + // NOTE (mristin): + // The root element might be self-closing (*e.g.*, ``) + // if it has no content, in which case there is no separate closing tag to + // corrupt. We handle the two cases distinctly. + const selfClosingRegex = new RegExp("]*/>)"); + const selfClosingMatch = text.match(selfClosingRegex); + + let brokenText: string; + if (selfClosingMatch !== null && selfClosingMatch.index !== undefined) { + brokenText = + text.slice(0, selfClosingMatch.index) + + " { + const propertyName = "name"; + + const pth = path.join( + TestCommon.TEST_DATA_DIR, "Xml", "Expected", "someItem", "minimal.xml" + ); + const [document, parseError] = parseXml(fs.readFileSync(pth, "utf-8")); + if (parseError !== null) { + throw new Error(`Invalid XML fixture: ${parseError}`); + } + + const root = document.documentElement; + if (root.tagName !== "someItem") { + throw new Error( + `Expected root element , ` + + `got <${root.tagName}>` + ); + } + + const property = Array.from(root.children).find( + (child) => child.tagName === propertyName + ); + + if (property === undefined) { + throw new Error(`Failed to find property element: ${propertyName}`); + } + + root.appendChild(property.cloneNode(true)); + + const brokenText = new XMLSerializer().serializeToString(document); + + expectDeserializationError(brokenText); +}); + +// This code has been automatically generated by aas-core-codegen. +// Do NOT edit or append. diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_documentation.txt b/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_documentation.txt new file mode 100644 index 000000000..9f755e913 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_documentation.txt @@ -0,0 +1 @@ +Provide dummy SDK. \ No newline at end of file diff --git a/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_identifier.txt b/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_identifier.txt new file mode 100644 index 000000000..edcbd8ba9 --- /dev/null +++ b/dev/test_data/main/typescript/expected/variance_of_containers/input/snippets/package_identifier.txt @@ -0,0 +1 @@ +@dummy-works/dummy diff --git a/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/schema.xsd b/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/schema.xsd new file mode 100644 index 000000000..b25f41e08 --- /dev/null +++ b/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/schema.xsd @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/stdout.txt b/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/stdout.txt new file mode 100644 index 000000000..2d755fc5a --- /dev/null +++ b/dev/test_data/main/xsd/expected/variance_of_containers/expected_output/stdout.txt @@ -0,0 +1 @@ +Code generated to: diff --git a/dev/test_data/main/xsd/expected/variance_of_containers/input/snippets/root_element.xml b/dev/test_data/main/xsd/expected/variance_of_containers/input/snippets/root_element.xml new file mode 100644 index 000000000..392e7c032 --- /dev/null +++ b/dev/test_data/main/xsd/expected/variance_of_containers/input/snippets/root_element.xml @@ -0,0 +1,8 @@ + + + diff --git a/dev/test_data/parse/common_meta_models/variance_of_containers/expected_symbol_table.txt b/dev/test_data/parse/common_meta_models/variance_of_containers/expected_symbol_table.txt new file mode 100644 index 000000000..c400ec748 --- /dev/null +++ b/dev/test_data/parse/common_meta_models/variance_of_containers/expected_symbol_table.txt @@ -0,0 +1,2252 @@ +UnverifiedSymbolTable( + our_types=[ + AbstractClass( + name='Item', + inheritances=[], + properties=[ + Property( + name='name', + type_annotation=AtomicTypeAnnotation( + identifier='str', + node=...), + description=None, + json_name=None, + xml_name=None, + node=...), + Property( + name='nickname', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + AtomicTypeAnnotation( + identifier='str', + node=...)], + node=...), + description=None, + json_name=None, + xml_name=None, + node=...)], + methods=[ + ConstructorToBeUnderstood( + name='__init__', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='name', + type_annotation=AtomicTypeAnnotation( + identifier='str', + node=...), + default=None, + node=...), + Argument( + name='nickname', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + AtomicTypeAnnotation( + identifier='str', + node=...)], + node=...), + default=Default( + node=...), + node=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + node=..., + arguments_by_name=..., + body=...)], + invariants=[], + serialization=Serialization( + with_model_type=True), + description=None, + node=..., + properties_by_name=..., + methods_by_name=...), + ConcreteClass( + name='Some_item', + inheritances=[ + 'Item'], + properties=[], + methods=[ + ConstructorToBeUnderstood( + name='__init__', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='name', + type_annotation=AtomicTypeAnnotation( + identifier='str', + node=...), + default=None, + node=...), + Argument( + name='nickname', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + AtomicTypeAnnotation( + identifier='str', + node=...)], + node=...), + default=Default( + node=...), + node=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + node=..., + arguments_by_name=..., + body=...)], + invariants=[], + serialization=None, + description=None, + node=..., + properties_by_name=..., + methods_by_name=...), + ConcreteClass( + name='Special_item', + inheritances=[ + 'Some_item'], + properties=[ + Property( + name='level', + type_annotation=AtomicTypeAnnotation( + identifier='int', + node=...), + description=None, + json_name=None, + xml_name=None, + node=...)], + methods=[ + ConstructorToBeUnderstood( + name='__init__', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='name', + type_annotation=AtomicTypeAnnotation( + identifier='str', + node=...), + default=None, + node=...), + Argument( + name='level', + type_annotation=AtomicTypeAnnotation( + identifier='int', + node=...), + default=None, + node=...), + Argument( + name='nickname', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + AtomicTypeAnnotation( + identifier='str', + node=...)], + node=...), + default=Default( + node=...), + node=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + node=..., + arguments_by_name=..., + body=...)], + invariants=[], + serialization=None, + description=None, + node=..., + properties_by_name=..., + methods_by_name=...), + ConcreteClass( + name='Container', + inheritances=[], + properties=[ + Property( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + description=None, + json_name=None, + xml_name=None, + node=...), + Property( + name='some_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Some_item', + node=...)], + node=...), + description=None, + json_name=None, + xml_name=None, + node=...), + Property( + name='special_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Special_item', + node=...)], + node=...)], + node=...), + description=None, + json_name=None, + xml_name=None, + node=...)], + methods=[ + UnderstoodMethod( + name='count_items', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='int', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=True, + body=[ + textwrap.dedent("""\ + Return( + value=FunctionCall( + name='len', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + ConstructorToBeUnderstood( + name='__init__', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='some_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Some_item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='special_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + SubscriptedTypeAnnotation( + identifier='List', + subscripts=[ + AtomicTypeAnnotation( + identifier='Special_item', + node=...)], + node=...)], + node=...), + default=Default( + node=...), + node=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + node=..., + arguments_by_name=..., + body=...)], + invariants=[ + Invariant( + description='Each special item must be among the special items.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=All( + generator=ForEach( + variable=Name( + identifier='special_item', + original_node=...), + iteration=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + condition=FunctionCall( + name='contains_special_item', + args=[ + Name( + identifier='special_item', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='If there are some items, they must overlap with the items.', + body=textwrap.dedent("""\ + Or( + values=[ + Comparison( + left=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...), + op='EQ', + right=Constant( + value=0, + original_node=...), + original_node=...), + FunctionCall( + name='items_overlap', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Some items must be among the items.', + body=textwrap.dedent("""\ + All( + generator=ForEach( + variable=Name( + identifier='some_item', + original_node=...), + iteration=Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + original_node=...), + condition=FunctionCall( + name='contains_some_item', + args=[ + Name( + identifier='some_item', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Names of some items and of special items must be unique through aliases.', + body=textwrap.dedent("""\ + FunctionCall( + name='aliased_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Names of the items and of some items must be disjoint.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_disjoint', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Constant( + value='x', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='If there is a special item with level, the last item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=FunctionCall( + name='some_special_item_has_level', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + consequent=FunctionCall( + name='last_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Levels of the special items among the items must be positive.', + body=textwrap.dedent("""\ + FunctionCall( + name='levels_of_special_items_are_positive', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Nicknames of some items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='nicknames_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Count of the items must match the number of the items.', + body=textwrap.dedent("""\ + Comparison( + left=MethodCall( + member=Member( + instance=Name( + identifier='self', + original_node=...), + name='count_items', + original_node=...), + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + op='EQ', + right=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Names of the special items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='optional_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='The first special item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=FunctionCall( + name='first_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Levels of the special items must be positive.', + body=textwrap.dedent("""\ + Implication( + antecedent=IsNotNone( + value=Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...), + original_node=...), + consequent=FunctionCall( + name='levels_are_positive', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='If there is a special item with level, the first item must be special.', + body=textwrap.dedent("""\ + Implication( + antecedent=FunctionCall( + name='some_special_item_has_level', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + consequent=FunctionCall( + name='first_is_special', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...), + original_node=...)"""), + node=...), + Invariant( + description='Names of some items must be non-empty.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_non_empty', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Names of some items and of special items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='some_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='some_items', + original_node=...), + Member( + instance=Name( + identifier='self', + original_node=...), + name='special_items', + original_node=...)], + original_node=...)"""), + node=...), + Invariant( + description='Names of the items must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='items', + original_node=...)], + original_node=...)"""), + node=...)], + serialization=None, + description=None, + node=..., + properties_by_name=..., + methods_by_name=...), + ConcreteClass( + name='Wrapper', + inheritances=[], + properties=[ + Property( + name='container', + type_annotation=AtomicTypeAnnotation( + identifier='Container', + node=...), + description=None, + json_name=None, + xml_name=None, + node=...)], + methods=[ + ConstructorToBeUnderstood( + name='__init__', + verification=False, + visibility='PUBLIC', + arguments=[ + Argument( + name='self', + type_annotation=SelfTypeAnnotation(), + default=None, + node=...), + Argument( + name='container', + type_annotation=AtomicTypeAnnotation( + identifier='Container', + node=...), + default=None, + node=...)], + returns=None, + description=None, + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + node=..., + arguments_by_name=..., + body=...)], + invariants=[ + Invariant( + description='Names of some items of the container must be unique.', + body=textwrap.dedent("""\ + FunctionCall( + name='container_names_are_unique', + args=[ + Member( + instance=Name( + identifier='self', + original_node=...), + name='container', + original_node=...)], + original_node=...)"""), + node=...)], + serialization=None, + description=None, + node=..., + properties_by_name=..., + methods_by_name=...)], + named_unions=[], + constants=[], + verification_functions=[ + UnderstoodMethod( + name='names_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='name_set', + original_node=...), + value=FunctionCall( + name='set', + args=[], + original_node=...), + annotation=Index( + collection=Name( + identifier='Set', + original_node=...), + index=Name( + identifier='str', + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + For( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=IsIn( + member=Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...), + container=Name( + identifier='name_set', + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...), + ExpressionStatement( + expression=MethodCall( + member=Member( + instance=Name( + identifier='name_set', + original_node=...), + name='add', + original_node=...), + args=[ + Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...)], + original_node=...), + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=True, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='names_are_non_empty', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=All( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + condition=Comparison( + left=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...)], + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='some_special_item_has_level', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=Any( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + condition=And( + values=[ + IsInstance( + value=Name( + identifier='item', + original_node=...), + classes=[ + Name( + identifier='Special_item', + original_node=...)], + original_node=...), + Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='level', + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...)], + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='first_is_special', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + If( + branches=[ + IfBranch( + condition=Comparison( + left=FunctionCall( + name='len', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...), + op='EQ', + right=Constant( + value=0, + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)"""), + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='first', + original_node=...), + value=Index( + collection=Name( + identifier='items', + original_node=...), + index=Constant( + value=0, + original_node=...), + original_node=...), + annotation=None, + original_node=...)"""), + textwrap.dedent("""\ + If( + branches=[ + IfBranch( + condition=IsInstance( + value=Name( + identifier='first', + original_node=...), + classes=[ + Name( + identifier='Special_item', + original_node=...)], + original_node=...), + body=[ + Return( + value=Comparison( + left=Member( + instance=Name( + identifier='first', + original_node=...), + name='level', + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='nicknames_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='nickname_set', + original_node=...), + value=FunctionCall( + name='set', + args=[], + original_node=...), + annotation=Index( + collection=Name( + identifier='Set', + original_node=...), + index=Name( + identifier='str', + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + For( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + body=[ + Assignment( + target=Name( + identifier='nickname', + original_node=...), + value=Member( + instance=Name( + identifier='item', + original_node=...), + name='nickname', + original_node=...), + annotation=None, + original_node=...), + If( + branches=[ + IfBranch( + condition=IsNone( + value=Name( + identifier='nickname', + original_node=...), + original_node=...), + body=[ + Continue( + original_node=...)], + original_node=...)], + default=None, + original_node=...), + If( + branches=[ + IfBranch( + condition=IsIn( + member=Name( + identifier='nickname', + original_node=...), + container=Name( + identifier='nickname_set', + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...), + ExpressionStatement( + expression=MethodCall( + member=Member( + instance=Name( + identifier='nickname_set', + original_node=...), + name='add', + original_node=...), + args=[ + Name( + identifier='nickname', + original_node=...)], + original_node=...), + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=True, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='levels_of_special_items_are_positive', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + For( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=IsInstance( + value=Name( + identifier='item', + original_node=...), + classes=[ + Name( + identifier='Special_item', + original_node=...)], + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='level', + original_node=...), + op='LE', + right=Constant( + value=0, + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=True, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='last_is_special', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + If( + branches=[ + IfBranch( + condition=Comparison( + left=FunctionCall( + name='len', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...), + op='EQ', + right=Constant( + value=0, + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)"""), + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='last', + original_node=...), + value=Index( + collection=Name( + identifier='items', + original_node=...), + index=Sub( + left=FunctionCall( + name='len', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...), + right=Constant( + value=1, + original_node=...), + original_node=...), + original_node=...), + annotation=Index( + collection=Name( + identifier='Final', + original_node=...), + index=Name( + identifier='Item', + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + If( + branches=[ + IfBranch( + condition=IsInstance( + value=Name( + identifier='last', + original_node=...), + classes=[ + Name( + identifier='Special_item', + original_node=...)], + original_node=...), + body=[ + Return( + value=Comparison( + left=Member( + instance=Name( + identifier='last', + original_node=...), + name='level', + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='names_are_disjoint', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='other_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Some_item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='shared_name', + type_annotation=AtomicTypeAnnotation( + identifier='str', + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=All( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + condition=All( + generator=ForEach( + variable=Name( + identifier='other_item', + original_node=...), + iteration=Name( + identifier='other_items', + original_node=...), + original_node=...), + condition=Or( + values=[ + Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...), + op='NE', + right=Member( + instance=Name( + identifier='other_item', + original_node=...), + name='name', + original_node=...), + original_node=...), + Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...), + op='EQ', + right=Name( + identifier='shared_name', + original_node=...), + original_node=...)], + original_node=...), + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='aliased_names_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='optional_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='alias', + original_node=...), + value=Name( + identifier='items', + original_node=...), + annotation=Index( + collection=Name( + identifier='Final', + original_node=...), + index=Index( + collection=Name( + identifier='Sequence', + original_node=...), + index=Name( + identifier='Item', + original_node=...), + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='alias_of_alias', + original_node=...), + value=Name( + identifier='alias', + original_node=...), + annotation=Index( + collection=Name( + identifier='Final', + original_node=...), + index=Index( + collection=Name( + identifier='Sequence', + original_node=...), + index=Name( + identifier='Item', + original_node=...), + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='optional_alias', + original_node=...), + value=Name( + identifier='optional_items', + original_node=...), + annotation=Index( + collection=Name( + identifier='Final', + original_node=...), + index=Index( + collection=Name( + identifier='Optional', + original_node=...), + index=Index( + collection=Name( + identifier='Sequence', + original_node=...), + index=Name( + identifier='Item', + original_node=...), + original_node=...), + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + For( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='alias', + original_node=...), + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=IsInstance( + value=Name( + identifier='item', + original_node=...), + classes=[ + Name( + identifier='Special_item', + original_node=...)], + original_node=...), + body=[ + If( + branches=[ + IfBranch( + condition=Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='level', + original_node=...), + op='LE', + right=Constant( + value=0, + original_node=...), + original_node=...), + body=[ + Return( + value=Constant( + value=False, + original_node=...), + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)], + default=None, + original_node=...)], + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=And( + values=[ + All( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='alias_of_alias', + original_node=...), + original_node=...), + condition=Comparison( + left=FunctionCall( + name='len', + args=[ + Member( + instance=Name( + identifier='item', + original_node=...), + name='name', + original_node=...)], + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...), + original_node=...), + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='alias_of_alias', + original_node=...)], + original_node=...), + FunctionCall( + name='optional_names_are_unique', + args=[ + Name( + identifier='optional_alias', + original_node=...)], + original_node=...), + Or( + values=[ + IsNone( + value=Name( + identifier='optional_alias', + original_node=...), + original_node=...), + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='optional_alias', + original_node=...)], + original_node=...)], + original_node=...)], + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='contains_some_item', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='some_item', + type_annotation=AtomicTypeAnnotation( + identifier='Some_item', + node=...), + default=None, + node=...), + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=IsIn( + member=Name( + identifier='some_item', + original_node=...), + container=Name( + identifier='items', + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='items_overlap', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='other_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Some_item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=Any( + generator=ForEach( + variable=Name( + identifier='other_item', + original_node=...), + iteration=Name( + identifier='other_items', + original_node=...), + original_node=...), + condition=IsIn( + member=Name( + identifier='other_item', + original_node=...), + container=Name( + identifier='items', + original_node=...), + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='contains_special_item', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='special_item', + type_annotation=AtomicTypeAnnotation( + identifier='Special_item', + node=...), + default=None, + node=...), + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Special_item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=IsIn( + member=Name( + identifier='special_item', + original_node=...), + container=Name( + identifier='items', + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='container_names_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='container', + type_annotation=AtomicTypeAnnotation( + identifier='Container', + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='some_items', + original_node=...), + value=Member( + instance=Name( + identifier='container', + original_node=...), + name='some_items', + original_node=...), + annotation=None, + original_node=...)"""), + textwrap.dedent("""\ + Assignment( + target=Name( + identifier='all_some_items', + original_node=...), + value=Member( + instance=Name( + identifier='container', + original_node=...), + name='some_items', + original_node=...), + annotation=Index( + collection=Name( + identifier='Final', + original_node=...), + index=Index( + collection=Name( + identifier='Sequence', + original_node=...), + index=Name( + identifier='Some_item', + original_node=...), + original_node=...), + original_node=...), + original_node=...)"""), + textwrap.dedent("""\ + Return( + value=And( + values=[ + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='some_items', + original_node=...)], + original_node=...), + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='all_some_items', + original_node=...)], + original_node=...), + FunctionCall( + name='optional_names_are_unique', + args=[ + Name( + identifier='all_some_items', + original_node=...)], + original_node=...)], + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='optional_names_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Item', + node=...)], + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=Or( + values=[ + IsNone( + value=Name( + identifier='items', + original_node=...), + original_node=...), + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='items', + original_node=...)], + original_node=...)], + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='some_names_are_unique', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='some_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Some_item', + node=...)], + node=...), + default=None, + node=...), + Argument( + name='special_items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Optional', + subscripts=[ + SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Special_item', + node=...)], + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=And( + values=[ + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='some_items', + original_node=...)], + original_node=...), + FunctionCall( + name='optional_names_are_unique', + args=[ + Name( + identifier='some_items', + original_node=...)], + original_node=...), + FunctionCall( + name='optional_names_are_unique', + args=[ + Name( + identifier='special_items', + original_node=...)], + original_node=...), + FunctionCall( + name='optional_names_are_unique', + args=[ + Constant( + value=None, + original_node=...)], + original_node=...), + Or( + values=[ + IsNone( + value=Name( + identifier='special_items', + original_node=...), + original_node=...), + FunctionCall( + name='names_are_unique', + args=[ + Name( + identifier='special_items', + original_node=...)], + original_node=...)], + original_node=...)], + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...), + UnderstoodMethod( + name='levels_are_positive', + verification=True, + visibility='PUBLIC', + arguments=[ + Argument( + name='items', + type_annotation=SubscriptedTypeAnnotation( + identifier='Sequence', + subscripts=[ + AtomicTypeAnnotation( + identifier='Special_item', + node=...)], + node=...), + default=None, + node=...)], + returns=AtomicTypeAnnotation( + identifier='bool', + node=...), + description=Description( + document=..., + node=...), + contracts=Contracts( + preconditions=[], + snapshots=[], + postconditions=[]), + non_mutating=False, + body=[ + textwrap.dedent("""\ + Return( + value=All( + generator=ForEach( + variable=Name( + identifier='item', + original_node=...), + iteration=Name( + identifier='items', + original_node=...), + original_node=...), + condition=Comparison( + left=Member( + instance=Name( + identifier='item', + original_node=...), + name='level', + original_node=...), + op='GT', + right=Constant( + value=0, + original_node=...), + original_node=...), + original_node=...), + original_node=...)""")], + node=..., + arguments_by_name=...)], + meta_model=MetaModel( + description=Description( + document=..., + node=...), + version='dummy', + xml_namespace='https://dummy.com')) \ No newline at end of file diff --git a/dev/tests/intermediate/test_type_inference.py b/dev/tests/intermediate/test_type_inference.py index cfe0d18c3..ee59a7462 100644 --- a/dev/tests/intermediate/test_type_inference.py +++ b/dev/tests/intermediate/test_type_inference.py @@ -4808,6 +4808,37 @@ def some_func(numbers: List[int]) -> bool: ) +_COLLECTION_INVARIANCE_EXPLANATION: Final[str] = ( + "The lists and the sets are invariant, so their items need to be of exactly " + "the same type. A mutable List[...] can not hold the instances of a subclass, " + "since the callee could insert an instance of another subclass into it. Only " + "a read-only Sequence[...] argument of a verification function accepts " + "the instances of a subclass, since the targets with invariant lists declare " + "it generic: Go with a type parameter, C++ with a template, C# with " + "IReadOnlyList and Java with List. The methods can not accept " + "them, since Go methods can not have type parameters, and C++ virtual methods " + "can not be templates. We do not copy the list implicitly either, as that " + "would cost linear time and memory on each call. Please declare the argument " + "with the exact type of the items." +) + + +def _generic_sequence_explanation(name: str) -> str: + return ( + f"The argument {name!r} is a read-only Sequence[...] of a class with " + f"descendants, so the targets declare it generic to accept the sequences " + f"of the descendants without a copy: Go with a type parameter, C++ with " + f"a template, C# with IReadOnlyList, and Java with List. " + f"Hence, we support it only as the collection of a for-loop or of " + f"all/any, the collection of an index, the container of in, the argument " + f"of len, the value of a nullness check, an argument of a verification " + f"function, and the value of an immutable alias of the very same type, " + f"*e.g.*, ``alias: Final[Sequence[...]] = {name}``. Elsewhere, *e.g.*, " + f"passed to a method, assigned to a mutable variable or copied with list, " + f"the targets would expect the non-generic list." + ) + + _SET_PRELUDE: Final[ str ] = """\ @@ -4835,6 +4866,286 @@ def collect(text: str, texts: Set[str]) -> bool: """ +class Test_invariant_list_arguments(unittest.TestCase): + @staticmethod + def source_with_verification(verification: str) -> str: + return f"""\ +class Abstract_item(DBC): + @implementation_specific + @non_mutating + def count(self, items: Sequence["Abstract_item"]) -> int: + pass + + +class Some_item(Abstract_item): + def __init__(self) -> None: + pass + + +@verification +def check(items: Sequence[Abstract_item]) -> bool: + return len(items) > 0 + + +@verification +def fill(items: List[Abstract_item]) -> bool: + return len(items) > 0 + + +@verification +@implementation_specific +def check_specifically(items: Sequence[Abstract_item]) -> bool: + pass + + +@verification +@implementation_specific +def check_optional_specifically(items: Optional[Sequence[Abstract_item]]) -> bool: + pass + + +@verification +@implementation_specific +def fill_specifically(items: List[Abstract_item]) -> bool: + pass + + +{verification} + + +__version__ = "dummy" +__xml_namespace__ = "https://dummy.com" +""" + + def test_sequence_of_same_items(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: List[Abstract_item]) -> bool: + return check(items) and fill(items) and check_specifically(items)""" + ) + ) + + def test_sequence_of_descendants_to_implementation_specific(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func( + items: Sequence[Some_item], optional_items: Optional[Sequence[Some_item]] +) -> bool: + return ( + check_specifically(items) + and check_optional_specifically(items) + and check_optional_specifically(optional_items) + )""" + ) + ) + + def test_sequence_of_descendants_to_transpiled_verification(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Sequence[Some_item]) -> bool: + return check(items)""" + ) + ) + + def test_supported_usages_of_generic_sequence(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func( + items: Sequence[Abstract_item], optional_items: Optional[Sequence[Abstract_item]] +) -> bool: + for item in items: + if not isinstance(item, Some_item): + return False + + if optional_items is not None: + if len(optional_items) > 0: + return check(optional_items) + + return ( + len(items) > 0 + and isinstance(items[0], Some_item) + and all(isinstance(item, Some_item) for item in items) + and check(items) + and check_specifically(items) + and check_optional_specifically(optional_items) + )""" + ) + ) + + def test_generic_sequence_to_method_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(item: Some_item, items: Sequence[Abstract_item]) -> bool: + return item.count(items) > 0""" + ), + expected_joined_message=_generic_sequence_explanation("items"), + ) + + def test_generic_sequence_assigned_to_variable_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Sequence[Abstract_item]) -> bool: + alias: Sequence[Abstract_item] = items + return len(alias) > 0""" + ), + expected_joined_message=_generic_sequence_explanation("items"), + ) + + def test_immutable_alias_of_generic_sequence(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func( + items: Sequence[Abstract_item], optional_items: Optional[Sequence[Abstract_item]] +) -> bool: + alias: Final[Sequence[Abstract_item]] = items + alias_of_alias: Final[Sequence[Abstract_item]] = alias + optional_alias: Final[Optional[Sequence[Abstract_item]]] = optional_items + return ( + all(isinstance(item, Some_item) for item in alias_of_alias) + and check(alias) + and (optional_alias is None or check(optional_alias)) + )""" + ) + ) + + def test_immutable_alias_of_another_type_fails(self) -> None: + # NOTE (mristin): + # The optional alias does not share the type annotation of the required + # generic sequence, so the targets would need to convert it. + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Sequence[Abstract_item]) -> bool: + alias: Final[Optional[Sequence[Abstract_item]]] = items + return alias is not None""" + ), + expected_joined_message=_generic_sequence_explanation("items"), + ) + + def test_copy_of_immutable_alias_of_generic_sequence_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Sequence[Abstract_item]) -> bool: + alias: Final[Sequence[Abstract_item]] = items + copy = list(alias) + return len(copy) > 0""" + ), + expected_joined_message=_generic_sequence_explanation("alias"), + ) + + def test_generic_sequence_copied_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Sequence[Abstract_item]) -> bool: + copy = list(items) + return len(copy) > 0""" + ), + expected_joined_message=_generic_sequence_explanation("items"), + ) + + def test_generic_sequence_as_container_of_in(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(item: Some_item, items: Sequence[Abstract_item]) -> bool: + return item in items""" + ) + ) + + def test_sequence_without_descendants_is_not_generic(self) -> None: + Test_with_smoke.execute( + Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(item: Some_item, items: Sequence[Some_item]) -> bool: + copy = list(items) + return len(copy) > 0""" + ) + ) + + def test_mutable_list_of_descendants_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: List[Some_item]) -> bool: + return fill(items)""" + ), + expected_joined_message=( + "The argument 'items' of the verification function 'fill' is " + "of type List[Abstract_item], but got: List[Some_item]. " + + _COLLECTION_INVARIANCE_EXPLANATION + ), + ) + + def test_mutable_list_of_descendants_to_implementation_specific_fails( + self, + ) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: List[Some_item]) -> bool: + return fill_specifically(items)""" + ), + expected_joined_message=( + "The argument 'items' of the verification function " + "'fill_specifically' is of type List[Abstract_item], but got: " + "List[Some_item]. " + _COLLECTION_INVARIANCE_EXPLANATION + ), + ) + + def test_optional_sequence_of_descendants_to_non_optional_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(items: Optional[Sequence[Some_item]]) -> bool: + return check_specifically(items)""" + ), + expected_joined_message=( + "The argument 'items' of the verification function " + "'check_specifically' is of type List[Abstract_item], but got: " + "Optional[List[Some_item]]. " + _COLLECTION_INVARIANCE_EXPLANATION + ), + ) + + def test_sequence_of_descendants_to_method_fails(self) -> None: + Test_with_smoke().expect_type_inference_to_fail( + source=Test_invariant_list_arguments.source_with_verification( + """\ +@verification +def some_func(item: Some_item, items: Sequence[Some_item]) -> bool: + return item.count(items) > 0""" + ), + expected_joined_message=( + "The argument 'items' of the method 'count' is of type " + "List[Abstract_item], but got: List[Some_item]. " + + _COLLECTION_INVARIANCE_EXPLANATION + ), + ) + + class Test_set(unittest.TestCase): def expect_type_inference_to_fail(self, body: str, expected_message: str) -> None: """Expect the type inference of the verification functions to fail.""" @@ -5160,7 +5471,8 @@ def some_func(number: int) -> bool: """, expected_message=( "The argument 'texts' of the verification function 'is_in' is of " - "type Set[str], but got: Set[int]" + "type Set[str], but got: Set[int]. " + + _COLLECTION_INVARIANCE_EXPLANATION ), ) @@ -5173,7 +5485,8 @@ def some_func(texts: Sequence[str]) -> bool: """, expected_message=( "The argument 'texts' of the verification function 'is_in' is of " - "type Set[str], but got: List[str]" + "type Set[str], but got: List[str]. " + + _COLLECTION_INVARIANCE_EXPLANATION ), ) @@ -5801,6 +6114,49 @@ def expect_errors(self, methods: str, expected_messages: List[str]) -> None: Test_method.infer(Test_method.source_with_methods(methods)), ) + def test_sequence_of_descendants_to_method_fails(self) -> None: + # NOTE (mristin): + # The arguments of the methods are never generic, since Go methods can not + # have type parameters, and C++ virtual methods can not be templates. + self.expect_errors( + methods="""\ +@non_mutating +def count_parents(self, parents: Sequence["Parent"]) -> int: + return len(parents) + +@non_mutating +def count_children(self, children: Sequence["Child"]) -> int: + return self.count_parents(children)""", + expected_messages=[ + "The argument 'parents' of the method 'count_parents' is of type " + "List[Parent], but got: List[Child]. " + + _COLLECTION_INVARIANCE_EXPLANATION, + "The method '__parent_private' of the class 'Child' is private, " + "so it can be only called on self from the methods of the class", + ], + ) + + def test_sequence_argument_of_method_is_not_generic(self) -> None: + # NOTE (mristin): + # The usages of the sequence arguments of the methods are not restricted, + # in contrast to the generic sequences of the verification functions, as + # the methods take the sequences of the exact items. + self.expect_errors( + methods="""\ +@non_mutating +def count_parents(self, parents: Sequence["Parent"]) -> int: + return len(parents) + +@non_mutating +def count_copies(self, parents: Sequence["Parent"]) -> int: + copies = list(parents) + return self.count_parents(parents) + len(copies)""", + expected_messages=[ + "The method '__parent_private' of the class 'Child' is private, " + "so it can be only called on self from the methods of the class", + ], + ) + def test_mutating_method_on_self(self) -> None: self.expect_errors( methods="""\ diff --git a/dev/tests/test_main.py b/dev/tests/test_main.py index 1dc7e4a56..2bf1dcc68 100644 --- a/dev/tests/test_main.py +++ b/dev/tests/test_main.py @@ -471,6 +471,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.CPP, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.CPP, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.CPP, case_name="visibility" @@ -496,6 +502,12 @@ def test_unexpected_conflict_between_underlying_of_function_and_class( case_name="conflict_between_underlying_of_function_and_class", ) + def test_unexpected_public_generic_verification_calls_internal(self) -> None: + self._run_unexpected_test( + target=aas_core_codegen.main.Target.CPP, + case_name="public_generic_verification_calls_internal", + ) + class Test_csharp(_TestCase): def test_expected_aas_core_meta_v3(self) -> None: @@ -697,6 +709,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.CSHARP, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.CSHARP, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.CSHARP, case_name="visibility" @@ -915,6 +933,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.GOLANG, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.GOLANG, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.GOLANG, case_name="visibility" @@ -1140,6 +1164,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.JAVA, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.JAVA, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.JAVA, case_name="visibility" @@ -1385,6 +1415,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.JSONSCHEMA, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.JSONSCHEMA, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.JSONSCHEMA, case_name="visibility" @@ -1591,6 +1627,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.PYTHON, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.PYTHON, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.PYTHON, case_name="visibility" @@ -1822,6 +1864,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.TYPESCRIPT, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.TYPESCRIPT, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.TYPESCRIPT, case_name="visibility" @@ -2050,6 +2098,12 @@ def test_expected_unions(self) -> None: target=aas_core_codegen.main.Target.XSD, case_name="unions" ) + def test_expected_variance_of_containers(self) -> None: + self._run_expected_test( + target=aas_core_codegen.main.Target.XSD, + case_name="variance_of_containers", + ) + def test_expected_visibility(self) -> None: self._run_expected_test( target=aas_core_codegen.main.Target.XSD, case_name="visibility"