This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/674-use-properties-type-in-filter in repository https://gitbox.apache.org/repos/asf/celix.git
commit 09effe20b9b4d7a432f10624c19f3e744ea3b662 Author: Pepijn Noltes <[email protected]> AuthorDate: Tue Nov 28 22:36:24 2023 +0100 Refactor filter to use typed properties, improve quality and test coverage --- libs/utils/gtest/CMakeLists.txt | 2 + libs/utils/gtest/src/CxxFilterTestSuite.cc | 7 +- .../gtest/src/FilterErrorInjectionTestSuite.cc | 165 ++++ libs/utils/gtest/src/FilterTestSuite.cc | 78 +- libs/utils/include/celix/Filter.h | 19 +- libs/utils/include/celix_filter.h | 4 +- libs/utils/src/filter.c | 911 +++++++++++---------- 7 files changed, 736 insertions(+), 450 deletions(-) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index bfb9a553..8b6381dc 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -99,6 +99,7 @@ if (EI_TESTS) src/PropertiesErrorInjectionTestSuite.cc src/VersionErrorInjectionTestSuite.cc src/HashMapErrorInjectionTestSuite.cc + src/FilterErrorInjectionTestSuite.cc ) target_link_libraries(test_utils_with_ei PRIVATE Celix::zip_ei @@ -114,6 +115,7 @@ if (EI_TESTS) Celix::string_hash_map_ei Celix::long_hash_map_ei Celix::version_ei + Celix::array_list_ei GTest::gtest GTest::gtest_main ) target_include_directories(test_utils_with_ei PRIVATE ../src) #for version_private (needs refactoring of test) diff --git a/libs/utils/gtest/src/CxxFilterTestSuite.cc b/libs/utils/gtest/src/CxxFilterTestSuite.cc index 0389d4b6..549eabba 100644 --- a/libs/utils/gtest/src/CxxFilterTestSuite.cc +++ b/libs/utils/gtest/src/CxxFilterTestSuite.cc @@ -28,15 +28,10 @@ public: TEST_F(CxxFilterTestSuite, CreateDestroy) { celix::Filter filter{}; - EXPECT_TRUE(filter.empty()); - EXPECT_TRUE(filter.getCFilter() == nullptr); - EXPECT_TRUE(filter.getFilterString().empty()); + EXPECT_EQ(filter.getFilterString(), "(|)"); //match all filter } TEST_F(CxxFilterTestSuite, FilterString) { - celix::Filter filter1{}; - EXPECT_EQ(std::string{}, filter1.getFilterString()); - celix::Filter filter2{"(key=value)"}; EXPECT_FALSE(filter2.empty()); EXPECT_EQ(std::string{"(key=value)"}, filter2.getFilterString()); diff --git a/libs/utils/gtest/src/FilterErrorInjectionTestSuite.cc b/libs/utils/gtest/src/FilterErrorInjectionTestSuite.cc new file mode 100644 index 00000000..cf676708 --- /dev/null +++ b/libs/utils/gtest/src/FilterErrorInjectionTestSuite.cc @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#include <gtest/gtest.h> + +#include "celix_filter.h" +#include "celix_utils.h" +#include "celix_err.h" + +#include "celix_array_list_ei.h" +#include "malloc_ei.h" +#include "stdio_ei.h" + +class FilterErrorInjectionTestSuite : public ::testing::Test { + public: + FilterErrorInjectionTestSuite() { + celix_err_resetErrors(); + celix_ei_expect_celix_arrayList_create(nullptr, 0, nullptr); + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_ei_expect_open_memstream(nullptr, 0, nullptr); + celix_ei_expect_fclose(nullptr, 0, 0); + } + + ~FilterErrorInjectionTestSuite() override { + celix_err_printErrors(stderr, nullptr, nullptr); + } +}; + +TEST_F(FilterErrorInjectionTestSuite, ErrorWithArrayListCreateTest) { + //Given an error injection for celix_arrayList_create + celix_ei_expect_celix_arrayList_create((void*)celix_filter_create, 3, nullptr); + //When creating a filter with a AND filter node + const char* filterStr = "(&(key1=value1)(key2=value2))"; + //Then the filter creation should fail, because it cannot create a children list for the AND filter node + celix_filter_t* filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for celix_arrayList_create + celix_ei_expect_celix_arrayList_create((void*)celix_filter_create, 3, nullptr); + //When creating a filter with a NOT filter node + filterStr = "(!(key1=value1))"; + //Then the filter creation should fail, because it cannot create a children list for the NOT filter node + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for celix_arrayList_create + celix_ei_expect_celix_arrayList_create((void*)celix_filter_create, 4, nullptr); + //When creating a filter with a substring + filterStr = "(key1=*val*)"; + //Then the filter creation should fail, because it cannot create a children list for the substring filter node + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); +} + +TEST_F(FilterErrorInjectionTestSuite, ErrorWithCallocTest) { + //Given an error injection for calloc + celix_ei_expect_calloc((void*)celix_filter_create, 3, nullptr); + //When creating a filter with a AND filter node + const char* filterStr = "(&(key1=value1)(key2=value2))"; + //Then the filter creation should fail, because it cannot calloc mem for a filter node + celix_filter_t* filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for calloc + celix_ei_expect_calloc((void*)celix_filter_create, 3, nullptr); + //When creating a filter with a NOT filter node + filterStr = "(!(key1=value1))"; + //Then the filter creation should fail, because it cannot calloc mem for a filter node + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for calloc + celix_ei_expect_calloc((void*)celix_filter_create, 3, nullptr); + //When creating a filter with a no compound filter node + filterStr = "(key1=value1)"; + //Then the filter creation should fail, because it cannot calloc mem for a filter node + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for calloc + celix_ei_expect_calloc((void*)celix_filter_create, 1, nullptr); + //When creating a filter with + filterStr = "(key1=value1)"; + //Then the filter creation should fail, because it cannot calloc mem for an internal struct (converted types) + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); +} + +TEST_F(FilterErrorInjectionTestSuite, ErrorMemStreamTest) { + //Given an error injection for open_memstream + celix_ei_expect_open_memstream((void*)celix_filter_create, 4, nullptr); + //When creating a filter with a parseable attribute (or attr value) + const char* filterStr = "(key1=value1)"; + //Then the filter creation should fail, because it cannot open a memstream to create the attribute string. + celix_filter_t* filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for open_memstream + celix_ei_expect_open_memstream((void*)celix_filter_create, 5, nullptr); + //When creating a filter with a sub string with an any part + filterStr = "(key1=*val*)"; + //Then the filter creation should fail, because it cannot open a memstream to create an any part + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); +} + +TEST_F(FilterErrorInjectionTestSuite, ErrorFcloseTest) { + //Given an error injection for fclose + celix_ei_expect_fclose((void*)celix_filter_create, 4, EOF); + //When creating a filter with a parseable attribute (or attr value) + const char* filterStr = "(key1=value1)"; + //Then the filter creation should fail, because it cannot close the (mem)stream. + celix_filter_t* filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for fclose + celix_ei_expect_fclose((void*)celix_filter_create, 5, EOF); + //When creating a filter with a sub string with an any part + filterStr = "(key1=*val*)"; + //Then the filter creation should fail, because it cannot close the (mem)stream. + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); +} + +TEST_F(FilterErrorInjectionTestSuite, ErrorFputcTest) { + // Given an error injection for fputc + celix_ei_expect_fputc((void*)celix_filter_create, 4, EOF); + // When creating a filter with a parseable attribute (or attr value) + const char* filterStr = "(k=value1)"; + // Then the filter creation should fail, because it cannot fputc a char to the (mem)stream. + celix_filter_t* filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + // Given an error injection for fputc with ordinal 2 + celix_ei_expect_fputc((void*)celix_filter_create, 4, EOF, 2); + // When creating a filter with a parseable attribute (or attr value) + filterStr = "(k=value1)"; + // Then the filter creation should fail, because it cannot fputc a '\0' char to the (mem)stream. + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); + + //Given an error injection for fclose + celix_ei_expect_fputc((void*)celix_filter_create, 5, EOF); + //When creating a filter with a sub string with an any part + filterStr = "(key1=*val*)"; + //Then the filter creation should fail, because it cannot close the (mem)stream. + filter = celix_filter_create(filterStr); + EXPECT_EQ(nullptr, filter); +} diff --git a/libs/utils/gtest/src/FilterTestSuite.cc b/libs/utils/gtest/src/FilterTestSuite.cc index 0a493f73..e571349c 100644 --- a/libs/utils/gtest/src/FilterTestSuite.cc +++ b/libs/utils/gtest/src/FilterTestSuite.cc @@ -85,20 +85,19 @@ TEST_F(FilterTestSuite, MissingClosingBracketsCreateTest) { } TEST_F(FilterTestSuite, InvalidClosingBracketsCreateTest) { - char* str; celix_filter_t* filter; // test missing closing brackets in substring - str = celix_utils_strdup("(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3=at(tr3)))"); - filter = celix_filter_create(str); + filter = celix_filter_create("(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3=at(tr3)))"); ASSERT_TRUE(filter == nullptr); - free(str); // test missing closing brackets in value - str = celix_utils_strdup("(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3>=att(r3)))"); - filter = celix_filter_create(str); + filter = celix_filter_create("(&(test_attr1=attr1)(|(test_attr2=attr2)(test_attr3>=att(r3)))"); + ASSERT_TRUE(filter == nullptr); + + // test invalid AND ending + filter = celix_filter_create("(&("); ASSERT_TRUE(filter == nullptr); - free(str); } TEST_F(FilterTestSuite, MiscInvalidCreateTest) { @@ -131,14 +130,14 @@ TEST_F(FilterTestSuite, MiscInvalidCreateTest) { filter = celix_filter_create(str5); ASSERT_TRUE(filter == nullptr); - // test parsing a value with a escaped closing bracket "\)" + // test parsing a value with an escaped closing parenthesis "\ ")" const char* str6 = "(test_attr3>=strWith\\)inIt)"; filter = celix_filter_create(str6); ASSERT_TRUE(filter != nullptr); ASSERT_STREQ("strWith)inIt", (char*)filter->value); celix_filter_destroy(filter); - // test parsing a substring with an escaped closing bracket "\)" + // test parsing a substring with an escaped closing parenthesis "\" const char* str7 = "(test_attr3=strWith\\)inIt)"; filter = celix_filter_create(str7); ASSERT_TRUE(filter != nullptr); @@ -169,7 +168,7 @@ TEST_F(FilterTestSuite, MatchEqualTest) { filter = celix_filter_create(str); ASSERT_TRUE(filter == nullptr); result = celix_filter_match(filter, props); - ASSERT_TRUE(result); + ASSERT_FALSE(result); // cleanup celix_properties_destroy(props); @@ -465,6 +464,7 @@ TEST_F(FilterTestSuite, TypedPropertiesAndFilterTest) { TEST_F(FilterTestSuite, SubStringTest) { celix_autoptr(celix_properties_t) props = celix_properties_create(); celix_properties_set(props, "test", "John Bob Doe"); + celix_properties_set(props, "test2", "*ValueWithStar"); //test filter with matching subInitial celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(test=Jo*)"); @@ -487,9 +487,8 @@ TEST_F(FilterTestSuite, SubStringTest) { EXPECT_TRUE(celix_filter_match(filter5, props)); //test filter with un-matching subAny - //TODO fixme -// celix_autoptr(celix_filter_t) filter6 = celix_filter_create("(test=*Boo*)"); -// EXPECT_FALSE(celix_filter_match(filter6, props)); + celix_autoptr(celix_filter_t) filter6 = celix_filter_create("(test=*Boo*)"); + EXPECT_FALSE(celix_filter_match(filter6, props)); //test filter with matching subAny, subInitial and subFinal celix_autoptr(celix_filter_t) filter7 = celix_filter_create("(test=Jo*Bob*Doe)"); @@ -497,11 +496,62 @@ TEST_F(FilterTestSuite, SubStringTest) { //test filter with un-matching subAny, subInitial and subFinal celix_autoptr(celix_filter_t) filter8 = celix_filter_create("(test=Jo*Boo*Doe)"); + EXPECT_FALSE(celix_filter_match(filter8, props)); - //test filter with un-matching overlapping subAny, subInitial and subFinal + //test filter with un-matching overlapping subAny and subInitial celix_autoptr(celix_filter_t) filter9 = celix_filter_create("(test=John B*Bob*b Doe)"); + EXPECT_FALSE(celix_filter_match(filter9, props)); + + //test filter with un-matching overlapping subAny and subFinal + celix_autoptr(celix_filter_t) filter10 = celix_filter_create("(test=*Bob*b Doe)"); + EXPECT_FALSE(celix_filter_match(filter10, props)); + + //test filter with a starting escaped asterisk + celix_autoptr(celix_filter_t) filter11 = celix_filter_create("(test2=\\*Value*)"); + EXPECT_TRUE(celix_filter_match(filter11, props)); + + //test filter with an invalid substring + celix_autoptr(celix_filter_t) filter12 = celix_filter_create("(test=Bob*"); + EXPECT_EQ(nullptr, filter12); } +TEST_F(FilterTestSuite, CreateEmptyFilter) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + + celix_autoptr(celix_filter_t) filter1 = celix_filter_create(nullptr); //fallback to "(|)" + EXPECT_TRUE(filter1 != nullptr); + EXPECT_TRUE(celix_filter_match(filter1, props)); + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create(""); //fallback to "(|)" + EXPECT_TRUE(filter2 != nullptr); + EXPECT_TRUE(celix_filter_match(filter2, props)); + + celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(|)"); + EXPECT_TRUE(filter3 != nullptr); + EXPECT_TRUE(celix_filter_match(filter3, props)); +} + +TEST_F(FilterTestSuite, LogicalOperatorsWithNoCriteriaTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + + celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(&)"); + EXPECT_TRUE(filter1 != nullptr); + EXPECT_TRUE(celix_filter_match(filter1, props)); + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(|)"); + EXPECT_TRUE(filter2 != nullptr); + EXPECT_TRUE(celix_filter_match(filter2, props)); + + celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(!)"); //NOT with no criteria is not valid + EXPECT_TRUE(filter3 == nullptr); +} + + +TEST_F(FilterTestSuite, InvalidEscapeTest) { + EXPECT_EQ(nullptr, celix_filter_create("(\\")); //escape without following char +} + + #include "filter.h" TEST_F(FilterTestSuite, DeprecatedApiTest) { auto* f1 = filter_create("(test_attr1=attr1)"); diff --git a/libs/utils/include/celix/Filter.h b/libs/utils/include/celix/Filter.h index 97c10159..b9789ae5 100644 --- a/libs/utils/include/celix/Filter.h +++ b/libs/utils/include/celix/Filter.h @@ -23,6 +23,7 @@ #include <memory> #include "celix_filter.h" +#include "celix_err.h" #include "celix/Exception.h" #include "celix/Properties.h" @@ -50,7 +51,18 @@ namespace celix { */ class Filter { public: - Filter() : cFilter{createFilter("")} {} + /** + * @brief Create an empty ("()") filter based on the provided filter string. + * @param[in] filterStr The filter string. + * @throw celix::FilterException if the filter string is invalid also logs an error message is to celix_err. + */ + Filter() : cFilter{createFilter(nullptr)} {} + + /** + * @brief Create a filter based on the provided filter string. + * @param[in] filterStr The filter string. + * @throw celix::FilterException if the filter string is invalid also logs an error message is to celix_err. + */ explicit Filter(const std::string& filterStr) : cFilter{createFilter(filterStr.c_str())} {} @@ -157,12 +169,9 @@ namespace celix { private: static std::shared_ptr<celix_filter_t> createFilter(const char* filterStr) { - if (filterStr == nullptr || strnlen(filterStr, 1) == 0) { - return nullptr; - } auto* cf = celix_filter_create(filterStr); if (cf == nullptr) { - throw celix::FilterException{"Invalid LDAP filter '" + std::string{filterStr} + "'"}; + throw celix::FilterException{"Cannot create filter: '" + std::string{filterStr} + "'"}; } return std::shared_ptr<celix_filter_t>{cf, [](celix_filter_t *f) { celix_filter_destroy(f); diff --git a/libs/utils/include/celix_filter.h b/libs/utils/include/celix_filter.h index c8b0d7e0..1cdb8eb7 100644 --- a/libs/utils/include/celix_filter.h +++ b/libs/utils/include/celix_filter.h @@ -114,7 +114,7 @@ typedef struct celix_filter_struct { * * If the return status is NULL, an error message is logged to celix_err. * - * @param filterStr The filter string. + * @param filterStr The filter string. If NULL or "" a match-all "(|)" filter is created. * @return The created filter or NULL if the filter string is invalid. */ CELIX_UTILS_EXPORT celix_filter_t* celix_filter_create(const char* filterStr); @@ -133,7 +133,7 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_filter_t, celix_filter_destroy) * @brief Check whether the provided filter matches the provided properties. * @param[in] filter The filter. * @param[in] props The properties. - * @return True if the filter matches the properties, false otherwise. + * @return True if the filter matches the properties, false otherwise. If filter or props is NULL false is returned. */ CELIX_UTILS_EXPORT bool celix_filter_match(const celix_filter_t* filter, const celix_properties_t* props); diff --git a/libs/utils/src/filter.c b/libs/utils/src/filter.c index e1a20652..fafc1ee6 100644 --- a/libs/utils/src/filter.c +++ b/libs/utils/src/filter.c @@ -17,23 +17,24 @@ * under the License. */ +#include <assert.h> +#include <ctype.h> #include <stdio.h> -#include <string.h> #include <stdlib.h> -#include <ctype.h> -#include <assert.h> +#include <string.h> #include <utils.h> #include "celix_convert_utils.h" #include "celix_err.h" #include "celix_errno.h" #include "celix_filter.h" +#include "celix_stdio_cleanup.h" #include "celix_stdlib_cleanup.h" #include "celix_version.h" #include "filter.h" -//ignoring clang-tidy recursion warnings for this file, because filter uses recursion -//NOLINTBEGIN(misc-no-recursion) +// ignoring clang-tidy recursion warnings for this file, because filter uses recursion +// NOLINTBEGIN(misc-no-recursion) struct celix_filter_internal { bool convertedToLong; @@ -46,396 +47,486 @@ struct celix_filter_internal { bool boolValue; bool convertedToVersion; - celix_version_t *versionValue; + celix_version_t* versionValue; }; -static void filter_skipWhiteSpace(char* filterString, int* pos); -static celix_filter_t * filter_parseFilter(char* filterString, int* pos); -static celix_filter_t * filter_parseFilterComp(char* filterString, int* pos); -static celix_filter_t * filter_parseAndOrOr(char* filterString, celix_filter_operand_t andOrOr, int* pos); -static celix_filter_t * filter_parseNot(char* filterString, int* pos); -static celix_filter_t * filter_parseItem(char* filterString, int* pos); -static char * filter_parseAttr(char* filterString, int* pos); -static char * filter_parseValue(char* filterString, int* pos); -static celix_array_list_t* filter_parseSubstring(char* filterString, int* pos); - -static void filter_skipWhiteSpace(char* filterString, int* pos) { +static void celix_filter_skipWhiteSpace(char* filterString, int* pos); +static celix_filter_t* celix_filter_parseFilter(char* filterString, int* pos); +static celix_filter_t* celix_filter_parseFilterNode(char* filterString, int* pos); +static celix_filter_t* celix_filter_parseAndOrOr(char* filterString, celix_filter_operand_t andOrOr, int* pos); +static celix_filter_t* celix_filter_parseNot(char* filterString, int* pos); +static celix_filter_t* celix_filter_parseItem(char* filterString, int* pos); +static char* celix_filter_parseAttributeOrValue(const char* filterString, int* pos, bool parseAttribute); +static celix_array_list_t* celix_filter_parseSubstring(char* filterString, int* pos); +static bool celix_filter_isSubString(const char* filterString, int startPos); +static bool celix_filter_isNextNonWhiteSpaceChar(const char* filterString, int pos, char c); + +static void celix_filter_skipWhiteSpace(char* filterString, int* pos) { size_t length; for (length = strlen(filterString); (*pos < length) && isspace(filterString[*pos]);) { (*pos)++; } } -celix_filter_t * filter_create(const char* filterString) { - return celix_filter_create(filterString); +static bool celix_filter_isNextNonWhiteSpaceChar(const char* filterString, int pos, char c) { + for (int i = pos; filterString[i] != '\0'; i++) { + if (!isspace(filterString[i])) { + return filterString[i] == c; + } + } + return false; } -void filter_destroy(celix_filter_t * filter) { - return celix_filter_destroy(filter); -} +celix_filter_t* filter_create(const char* filterString) { return celix_filter_create(filterString); } -static celix_filter_t * filter_parseFilter(char* filterString, int* pos) { - filter_skipWhiteSpace(filterString, pos); - if (filterString[*pos] == '\0') { - //empty filter - celix_err_push("Filter Error: Cannot create LDAP filter from an empty string.\n"); - return NULL; - } else if (filterString[*pos] != '(') { - celix_err_pushf("Filter Error: Missing '(' in filter string '%s'.\n", filterString); +void filter_destroy(celix_filter_t* filter) { return celix_filter_destroy(filter); } + +static celix_filter_t* celix_filter_parseFilter(char* filterString, int* pos) { + celix_filter_skipWhiteSpace(filterString, pos); + if (filterString[*pos] != '(') { + celix_err_pushf("Filter Error: Missing '(' in filter string '%s'.", filterString); return NULL; } - (*pos)++; //eat '(' + (*pos)++; // eat '(' - celix_autoptr(celix_filter_t) filter = filter_parseFilterComp(filterString, pos); + celix_autoptr(celix_filter_t) filter = celix_filter_parseFilterNode(filterString, pos); - filter_skipWhiteSpace(filterString, pos); + celix_filter_skipWhiteSpace(filterString, pos); if (filterString[*pos] != ')') { - celix_err_pushf("Filter Error: Missing ')' in filter string '%s'.\n", filterString); + celix_err_pushf("Filter Error: Missing ')' in filter string '%s'.", filterString); return NULL; } - (*pos)++; //eat ')' - filter_skipWhiteSpace(filterString, pos); + (*pos)++; // eat ')' + celix_filter_skipWhiteSpace(filterString, pos); return celix_steal_ptr(filter); } -static celix_filter_t * filter_parseFilterComp(char * filterString, int * pos) { +static celix_filter_t* celix_filter_parseFilterNode(char* filterString, int* pos) { char c; - filter_skipWhiteSpace(filterString, pos); + celix_filter_skipWhiteSpace(filterString, pos); c = filterString[*pos]; switch (c) { - case '&': { - (*pos)++; - return filter_parseAndOrOr(filterString, CELIX_FILTER_OPERAND_AND, pos); - } - case '|': { - (*pos)++; - return filter_parseAndOrOr(filterString, CELIX_FILTER_OPERAND_OR, pos); - } - case '!': { - (*pos)++; - return filter_parseNot(filterString, pos); - } - default: { - return filter_parseItem(filterString, pos); - } + case '&': { + (*pos)++; + return celix_filter_parseAndOrOr(filterString, CELIX_FILTER_OPERAND_AND, pos); + } + case '|': { + (*pos)++; + return celix_filter_parseAndOrOr(filterString, CELIX_FILTER_OPERAND_OR, pos); + } + case '!': { + (*pos)++; + return celix_filter_parseNot(filterString, pos); + } + default: { + return celix_filter_parseItem(filterString, pos); + } } } -static celix_filter_t * filter_parseAndOrOr(char * filterString, celix_filter_operand_t andOrOr, int * pos) { +static celix_filter_t* celix_filter_parseAndOrOr(char* filterString, celix_filter_operand_t andOrOr, int* pos) { + celix_filter_skipWhiteSpace(filterString, pos); - filter_skipWhiteSpace(filterString, pos); - bool failure = false; + celix_autoptr(celix_filter_t) filter = (celix_filter_t*)calloc(1, sizeof(*filter)); + if (!filter) { + celix_err_push("Filter Error: Failed to allocate memory."); + return NULL; + } - if (filterString[*pos] != '(') { - fprintf(stderr, "Filter Error: Missing '('.\n"); + celix_autoptr(celix_array_list_t) children = celix_arrayList_create(); + if (!children) { + celix_err_push("Filter Error: Failed to allocate memory."); return NULL; } - celix_array_list_t *children = celix_arrayList_create(); - while(filterString[*pos] == '(') { - celix_filter_t * child = filter_parseFilter(filterString, pos); - if(child == NULL) { - failure = true; - break; + if (filterString[*pos] == ')') { + // empty and/or filter + } else if (filterString[*pos] != '(') { + celix_err_push("Filter Error: Missing '('."); + return NULL; + } else { + bool failure = false; + while (filterString[*pos] == '(') { + celix_filter_t* child = celix_filter_parseFilter(filterString, pos); + if (child == NULL) { + failure = true; + break; + } + celix_arrayList_add(children, child); } - celix_arrayList_add(children, child); - } - if(failure == true){ - for (int i = 0; i < celix_arrayList_size(children); ++i) { - celix_filter_t * f = celix_arrayList_get(children, i); - filter_destroy(f); + if (failure == true) { + for (int i = 0; i < celix_arrayList_size(children); ++i) { + celix_filter_t* f = celix_arrayList_get(children, i); + filter_destroy(f); + } + return NULL; } - celix_arrayList_destroy(children); - return NULL; } - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); filter->operand = andOrOr; - filter->children = children; + filter->children = celix_steal_ptr(children); - return filter; + return celix_steal_ptr(filter); } -static celix_filter_t * filter_parseNot(char * filterString, int * pos) { - celix_filter_t * child = NULL; - filter_skipWhiteSpace(filterString, pos); +static celix_filter_t* celix_filter_parseNot(char* filterString, int* pos) { + celix_filter_skipWhiteSpace(filterString, pos); - if (filterString[*pos] != '(') { - fprintf(stderr, "Filter Error: Missing '('.\n"); + char c = filterString[*pos]; + if (c != '(') { + celix_err_push("Filter Error: Missing '('."); return NULL; } - child = filter_parseFilter(filterString, pos); - if (child == NULL) { + celix_filter_t* child = celix_filter_parseFilter(filterString, pos); + if (!child) { return NULL; } + celix_array_list_t* children = celix_arrayList_create(); + if (!children) { + celix_err_push("Filter Error: Failed to allocate memory."); + celix_filter_destroy(child); + return NULL; + } celix_arrayList_add(children, child); - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - filter->operand = CELIX_FILTER_OPERAND_NOT; - filter->children = children; + celix_filter_t* filter = (celix_filter_t*)calloc(1, sizeof(*filter)); + if (!filter) { + celix_err_push("Filter Error: Failed to allocate memory."); + celix_filter_destroy(child); + celix_arrayList_destroy(children); + return NULL; + } + filter->operand = CELIX_FILTER_OPERAND_NOT; + filter->children = children; //note can be NULL return filter; } -static celix_filter_t * filter_parseItem(char * filterString, int * pos) { - char * attr = filter_parseAttr(filterString, pos); - if(attr == NULL){ +static celix_filter_t* celix_filter_parseItem(char* filterString, int* pos) { + celix_autofree char* attr = celix_filter_parseAttributeOrValue(filterString, pos, true); + if (!attr) { + celix_err_push("Filter Error: Missing attr."); return NULL; } - filter_skipWhiteSpace(filterString, pos); - switch(filterString[*pos]) { - case '~': { - if (filterString[*pos + 1] == '=') { - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - *pos += 2; - filter->operand = CELIX_FILTER_OPERAND_APPROX; - filter->attribute = attr; - filter->value = filter_parseValue(filterString, pos); - return filter; + celix_autoptr(celix_filter_t) filter = calloc(1, sizeof(*filter)); + if (!filter) { + celix_err_push("Filter Error: Failed to allocate memory."); + return NULL; + } + filter->attribute = celix_steal_ptr(attr); + + celix_filter_skipWhiteSpace(filterString, pos); + char op = filterString[*pos]; + switch (op) { + case '~': { + char secondOp = filterString[*pos + 1]; + if (secondOp == '=') { + *pos += 2; + filter->operand = CELIX_FILTER_OPERAND_APPROX; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; } - break; + return celix_steal_ptr(filter); } - case '>': { - if (filterString[*pos + 1] == '=') { - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - *pos += 2; - filter->operand = CELIX_FILTER_OPERAND_GREATEREQUAL; - filter->attribute = attr; - filter->value = filter_parseValue(filterString, pos); - return filter; + celix_err_pushf("Filter Error: Invalid operand char after ~. Expected `=` got `%c`", secondOp); + return NULL; + } + case '>': { + if (filterString[*pos + 1] == '=') { + *pos += 2; + filter->operand = CELIX_FILTER_OPERAND_GREATEREQUAL; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; } - else { - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - *pos += 1; - filter->operand = CELIX_FILTER_OPERAND_GREATER; - filter->attribute = attr; - filter->value = filter_parseValue(filterString, pos); - return filter; + return celix_steal_ptr(filter); + } else { + *pos += 1; + filter->operand = CELIX_FILTER_OPERAND_GREATER; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; } + return celix_steal_ptr(filter); } - case '<': { - if (filterString[*pos + 1] == '=') { - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - *pos += 2; - filter->operand = CELIX_FILTER_OPERAND_LESSEQUAL; - filter->attribute = attr; - filter->value = filter_parseValue(filterString, pos); - return filter; + } + case '<': { + if (filterString[*pos + 1] == '=') { + *pos += 2; + filter->operand = CELIX_FILTER_OPERAND_LESSEQUAL; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; } - else { - celix_filter_t * filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - *pos += 1; - filter->operand = CELIX_FILTER_OPERAND_LESS; - filter->attribute = attr; - filter->value = filter_parseValue(filterString, pos); - return filter; + return celix_steal_ptr(filter); + } else { + *pos += 1; + filter->operand = CELIX_FILTER_OPERAND_LESS; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; } + return celix_steal_ptr(filter); } - case '=': { - celix_filter_t * filter = NULL; - celix_array_list_t *subs; - if (filterString[*pos + 1] == '*') { - int oldPos = *pos; - *pos += 2; - filter_skipWhiteSpace(filterString, pos); - if (filterString[*pos] == ')') { - filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - filter->operand = CELIX_FILTER_OPERAND_PRESENT; - filter->attribute = attr; - filter->value = NULL; - return filter; - } - *pos = oldPos; + } + case '=': { + if (filterString[*pos + 1] == '*') { + int oldPos = *pos; + *pos += 2; + celix_filter_skipWhiteSpace(filterString, pos); + if (filterString[*pos] == ')') { + filter->operand = CELIX_FILTER_OPERAND_PRESENT; + filter->value = NULL; + return celix_steal_ptr(filter); } - filter = (celix_filter_t *) calloc(1, sizeof(*filter)); - (*pos)++; - subs = filter_parseSubstring(filterString, pos); - if(subs!=NULL){ - if (celix_arrayList_size(subs) == 1) { - char * string = (char *) celix_arrayList_get(subs, 0); - if (string != NULL) { - filter->operand = CELIX_FILTER_OPERAND_EQUAL; - filter->attribute = attr; - filter->value = string; - - celix_arrayList_clear(subs); - celix_arrayList_destroy(subs); - - return filter; - } - } + *pos = oldPos; + } + (*pos)++; + if (celix_filter_isSubString(filterString, *pos)) { + celix_array_list_t* subs = celix_filter_parseSubstring(filterString, pos); + if (!subs) { + return NULL; } filter->operand = CELIX_FILTER_OPERAND_SUBSTRING; - filter->attribute = attr; filter->children = subs; - return filter; - } - } - fprintf(stderr, "Filter Error: Invalid operator.\n"); - free(attr); - return NULL; -} - -static char * filter_parseAttr(char * filterString, int * pos) { - char c; - int begin = *pos; - int end = *pos; - int length; - - filter_skipWhiteSpace(filterString, pos); - c = filterString[*pos]; - - while (c != '~' && c != '<' && c != '>' && c != '=' && c != '(' && c != ')') { - (*pos)++; - - if (!isspace(c)) { - end = *pos; + return celix_steal_ptr(filter); + } else { + filter->operand = CELIX_FILTER_OPERAND_EQUAL; + filter->value = celix_filter_parseAttributeOrValue(filterString, pos, false); + if (!filter->value) { + return NULL; + } + return celix_steal_ptr(filter); } - - c = filterString[*pos]; } - - length = end - begin; - - if (length == 0) { - fprintf(stderr, "Filter Error: Missing attr.\n"); + default: { + celix_err_pushf("Filter Error: Invalid operand char `%c`", op); return NULL; - } else { - char * attr = (char *) calloc(1, length+1); - strncpy(attr, filterString+begin, length); - attr[length] = '\0'; - return attr; + } } } -static char * filter_parseValue(char * filterString, int * pos) { - char *value = calloc(strlen(filterString) + 1, sizeof(*value)); - int keepRunning = 1; +static char* celix_filter_parseAttributeOrValue(const char* filterString, int* pos, bool parseAttribute) { + const char* name = parseAttribute ? "attribute" : "attribute value"; + celix_autofree char* value = NULL; + size_t valueSize = 0; + celix_autoptr(FILE) stream = open_memstream(&value, &valueSize); + if (!stream) { + celix_err_push("Filter Error: Failed to open mem stream."); + return NULL; + } + bool keepRunning = true; while (keepRunning) { char c = filterString[*pos]; switch (c) { - case ')': { - keepRunning = 0; - break; - } - case '(': { - fprintf(stderr, "Filter Error: Invalid value.\n"); - free(value); + case ')': { + if (parseAttribute) { + celix_err_pushf("Filter Error: Unexpected `)` char while parsing %s.", name); + fflush(stream); return NULL; } - case '\0':{ - fprintf(stderr, "Filter Error: Unclosed bracket.\n"); - free(value); - return NULL; + keepRunning = false; + break; + } + case '(': { + celix_err_pushf("Filter Error: Unexpected `(` char while parsing %s.", name); + fflush(stream); + return NULL; + } + case '\0': { + celix_err_pushf("Filter Error: Unexpected end of string while parsing %s.", name); + fflush(stream); + return NULL; + } + case '<': + case '>': + case '=': + case '~': { + if (parseAttribute) { + keepRunning = false; // done for attribute, valid for value + break; } - case '\\': { - (*pos)++; + /* no break */ + } + default: { + if (c == '\\') { + if (filterString[*pos + 1] == '\0') { + celix_err_pushf("Filter Error: Unexpected end of string while parsing %s.", name); + fflush(stream); + return NULL; + } + (*pos)++; // eat '\' c = filterString[*pos]; } - /* no break */ - default: { - char ch[2]; - ch[0] = c; - ch[1] = '\0'; - strcat(value, ch); - (*pos)++; - break; + int rc = fputc(c, stream); + if (rc == EOF) { + celix_err_push("Filter Error: Failed to write to stream."); + fflush(stream); + return NULL; } + (*pos)++; + break; } + } + } + + // end with \0 + int rc = fputc('\0', stream); + if (rc == EOF) { + celix_err_push("Filter Error: Failed to write to stream."); + fflush(stream); + return NULL; + } + + rc = fclose(celix_steal_ptr(stream)); + if (rc != 0) { + celix_err_push("Filter Error: Failed to close stream."); + return NULL; } - if (strlen(value) == 0) { - fprintf(stderr, "Filter Error: Missing value.\n"); - free(value); + if (celix_utils_strlen(value) == 0) { + celix_err_push("Filter Error: Empty value.\n"); return NULL; } - return value; + + return celix_steal_ptr(value); } -static celix_array_list_t* filter_parseSubstring(char * filterString, int * pos) { - char *sub = calloc(strlen(filterString) + 1, sizeof(*sub)); - celix_array_list_t* operands = celix_arrayList_create(); - int keepRunning = 1; +static bool celix_filter_isSubString(const char* filterString, int startPos) { + for (int i = startPos; filterString[i] != '\0' && filterString[i] != ')'; i++) { + if (filterString[i] == '*') { + return true; + } + } + return false; +} - while (keepRunning) { - char c = filterString[*pos]; - +celix_status_t celix_filter_parseSubstringAny(char* filterString, int* pos, char** out) { + celix_autofree char* any = NULL; + size_t anySize = 0; + celix_autoptr(FILE) stream = open_memstream(&any, &anySize); + if (!stream) { + celix_err_push("Filter Error: Failed to open mem stream."); + return CELIX_ENOMEM; + } - switch (c) { - case ')': { - if (strlen(sub) > 0) { - celix_arrayList_add(operands, strdup(sub)); - } - keepRunning = 0; - break; - } - case '\0':{ - fprintf(stderr, "Filter Error: Unclosed bracket.\n"); - keepRunning = false; - break; - } - case '(': { - fprintf(stderr, "Filter Error: Invalid value.\n"); - keepRunning = false; - break; - } - case '*': { - if (strlen(sub) > 0) { - celix_arrayList_add(operands, strdup(sub)); - } - sub[0] = '\0'; - celix_arrayList_add(operands, NULL); - (*pos)++; - break; - } - case '\\': { - (*pos)++; - c = filterString[*pos]; - } - /* no break */ - default: { - char ch[2]; - ch[0] = c; - ch[1] = '\0'; - strcat(sub, ch); - (*pos)++; - break; + int startPos = *pos; + while (filterString[*pos] != '\0' && filterString[*pos] != ')' && filterString[*pos] != '*') { + int rc; + if (filterString[*pos] == '\\') { + if (filterString[*pos + 1] == '\0') { + celix_err_push("Filter Error: Unexpected end of string while parsing attribute value."); + fflush(stream); + return CELIX_FILE_IO_EXCEPTION; } + (*pos)++; // eat '\' + rc = fputc(filterString[*pos], stream); + } else { + rc = fputc(filterString[*pos], stream); } + if (rc == EOF) { + celix_err_push("Filter Error: Failed to write to stream.\n"); + fflush(stream); + return CELIX_FILE_IO_EXCEPTION; + } + (*pos)++; } - free(sub); - if (celix_arrayList_size(operands) == 0) { - fprintf(stderr, "Filter Error: Missing value.\n"); - celix_arrayList_destroy(operands); + if (startPos == *pos) { + // empty any + *out = NULL; + return CELIX_SUCCESS; + } + + int rc = fclose(celix_steal_ptr(stream)); + if (rc != 0) { + celix_err_push("Filter Error: Failed to close stream."); + return CELIX_FILE_IO_EXCEPTION; + } + + *out = celix_steal_ptr(any); + return CELIX_SUCCESS; +} + +/** + * @brief Parses a substring filter. + * Returns a array list with at least 2 elements: The first element is the initial element, the last element is the + * final element. The initial and final element can be NULL. + * + * e.g.: + * - (foo=bar*) -> [bar, NULL] + * - (foo=*bar) -> [NULL, bar] + * - (foo=*bar*) -> [NULL, bar, NULL] + * - (foo=bar*bar) -> [bar, bar] + * - (foo=bar*bar*) -> [bar, bar, NULL] + */ +static celix_array_list_t* celix_filter_parseSubstring(char* filterString, int* pos) { + celix_array_list_t* subs = celix_arrayList_create(); + if (!subs) { + celix_err_push("Filter Error: Failed to allocate memory."); return NULL; } - return operands; + celix_filter_skipWhiteSpace(filterString, pos); + if (filterString[*pos] == '*') { + // initial substring is NULL + // eat '*' + (*pos)++; + celix_arrayList_add(subs, NULL); + } + + char* element = NULL; + celix_status_t status; + do { + status = celix_filter_parseSubstringAny(filterString, pos, &element); + if (element) { + celix_arrayList_add(subs, element); + } + + if (filterString[*pos] == '*' && celix_filter_isNextNonWhiteSpaceChar(filterString, *pos + 1, ')')) { + // final substring is NULL + (*pos)++; // eat '*' + celix_filter_skipWhiteSpace(filterString, pos); + celix_arrayList_add(subs, NULL); + break; + } else if (filterString[*pos] == '*') { + // eat '*' + (*pos)++; + } + } while (status == CELIX_SUCCESS && element); + + if (status != CELIX_SUCCESS) { + for (int i = 0; i < celix_arrayList_size(subs); ++i) { + char* e = celix_arrayList_get(subs, i); + free(e); + } + celix_arrayList_destroy(subs); + return NULL; + } + + celix_filter_skipWhiteSpace(filterString, pos); + return subs; } static bool celix_filter_isCompareOperand(celix_filter_operand_t operand) { - return operand == CELIX_FILTER_OPERAND_EQUAL || - operand == CELIX_FILTER_OPERAND_GREATER || - operand == CELIX_FILTER_OPERAND_LESS || - operand == CELIX_FILTER_OPERAND_GREATEREQUAL || - operand == CELIX_FILTER_OPERAND_LESSEQUAL; + return operand == CELIX_FILTER_OPERAND_EQUAL || operand == CELIX_FILTER_OPERAND_GREATER || + operand == CELIX_FILTER_OPERAND_LESS || operand == CELIX_FILTER_OPERAND_GREATEREQUAL || + operand == CELIX_FILTER_OPERAND_LESSEQUAL; } - static bool celix_filter_hasFilterChildren(celix_filter_t* filter) { - return filter->operand == CELIX_FILTER_OPERAND_AND || - filter->operand == CELIX_FILTER_OPERAND_OR || + return filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || filter->operand == CELIX_FILTER_OPERAND_NOT; } @@ -444,14 +535,19 @@ static bool celix_filter_hasFilterChildren(celix_filter_t* filter) { */ static celix_status_t celix_filter_compile(celix_filter_t* filter) { if (celix_filter_isCompareOperand(filter->operand)) { - filter->internal = malloc(sizeof(*filter->internal)); + filter->internal = calloc(1, sizeof(*filter->internal)); if (filter->internal == NULL) { + celix_err_push("Filter Error: Failed to allocate memory."); return CELIX_ENOMEM; } else { - filter->internal->longValue = celix_utils_convertStringToLong(filter->value, 0, &filter->internal->convertedToLong); - filter->internal->doubleValue = celix_utils_convertStringToDouble(filter->value, 0.0, &filter->internal->convertedToDouble); - filter->internal->versionValue = celix_utils_convertStringToVersion(filter->value, NULL, &filter->internal->convertedToVersion); - filter->internal->boolValue = celix_utils_convertStringToBool(filter->value, false, &filter->internal->convertedToBool); + filter->internal->longValue = + celix_utils_convertStringToLong(filter->value, 0, &filter->internal->convertedToLong); + filter->internal->doubleValue = + celix_utils_convertStringToDouble(filter->value, 0.0, &filter->internal->convertedToDouble); + filter->internal->versionValue = + celix_utils_convertStringToVersion(filter->value, NULL, &filter->internal->convertedToVersion); + filter->internal->boolValue = + celix_utils_convertStringToBool(filter->value, false, &filter->internal->convertedToBool); } } @@ -513,7 +609,7 @@ static int celix_filter_compareAttributeValue(const celix_filter_t* filter, cons return strcmp(entry->value, filter->value); } - //compare typed values + // compare typed values if (filter->internal->convertedToLong && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { return celix_filter_cmpLong(entry->typed.longValue, filter->internal->longValue); } else if (filter->internal->convertedToDouble && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { @@ -524,7 +620,7 @@ static int celix_filter_compareAttributeValue(const celix_filter_t* filter, cons return celix_version_compareTo(entry->typed.versionValue, filter->internal->versionValue); } - //check if the property string value can be converted to the filter value type + // check if the property string value can be converted to the filter value type bool propertyConverted; if (filter->internal->convertedToLong) { long val = celix_utils_convertStringToLong(entry->value, 0, &propertyConverted); @@ -550,100 +646,74 @@ static int celix_filter_compareAttributeValue(const celix_filter_t* filter, cons } } - //fallback on string compare + // fallback on string compare return strcmp(entry->value, filter->value); } -static bool filter_compareSubString(const celix_filter_t* filter, const celix_properties_entry_t* entry) { - const char* propertyValue = entry->value; - size_t pos = 0; - int size = celix_arrayList_size(filter->children); - for (size_t i = 0; i < size; i++) { - char * substr = (char *) celix_arrayList_get(filter->children, (int)i); +static bool celix_filter_matchSubString(const celix_filter_t* filter, const celix_properties_entry_t* entry) { + assert(filter->children && celix_arrayList_size(filter->children) >= 2); - if (i + 1 < size) { - if (substr == NULL) { - unsigned int index; - char * substr2 = (char *) celix_arrayList_get(filter->children, (int)(i + 1)); - if (substr2 == NULL) { - continue; - } - index = strcspn(propertyValue+pos, substr2); - if (index == strlen(propertyValue+pos)) { - return false; - } + size_t strLen = celix_utils_strlen(entry->value); + const char* initial = celix_arrayList_get(filter->children, 0); + const char* final = celix_arrayList_get(filter->children, celix_arrayList_size(filter->children) - 1); - pos = index + strlen(substr2); - if (i + 2 < size) { - i++; - } - } else { - unsigned int len = strlen(substr); - char * region = (char *)calloc(1, len+1); //TODO refactor filter compile to prevent calloc need - strncpy(region, propertyValue+pos, len); - region[len] = '\0'; - if (strcmp(region, substr) == 0) { - pos += len; - } else { - free(region); - return false; - } - free(region); - } - } else { - size_t len; - size_t begin; + const char* currentValue = entry->value; - if (substr == NULL) { - return true; - } - len = strlen(substr); - begin = strlen(propertyValue)-len; - return (strcmp(propertyValue+begin, substr) == 0); + if (initial) { + const char* found = strstr(entry->value, initial); + currentValue = found + celix_utils_strlen(initial); + if (!found || found != entry->value) { + return false; + } + } + + for (int i = 1; i < celix_arrayList_size(filter->children) - 1; i++) { + const char* substr = celix_arrayList_get(filter->children, i); + const char* found = strstr(currentValue, substr); + if (!found || found <= currentValue) { + return false; } + currentValue = found + celix_utils_strlen(substr); } + + if (final) { + const char* found = strstr(currentValue, final); + if (!found || found <= currentValue || found + celix_utils_strlen(final) != entry->value + strLen) { + return false; + } + } + return true; } static bool celix_filter_matchPropertyEntry(const celix_filter_t* filter, const celix_properties_entry_t* entry) { - if (filter == NULL || entry == NULL) { - return false; + switch (filter->operand) { + case CELIX_FILTER_OPERAND_SUBSTRING: + return celix_filter_matchSubString(filter, entry); + case CELIX_FILTER_OPERAND_APPROX: + return strcasecmp(entry->value, filter->value) == 0; + case CELIX_FILTER_OPERAND_EQUAL: + return celix_filter_compareAttributeValue(filter, entry) == 0; + case CELIX_FILTER_OPERAND_GREATER: + return celix_filter_compareAttributeValue(filter, entry) > 0; + case CELIX_FILTER_OPERAND_GREATEREQUAL: + return celix_filter_compareAttributeValue(filter, entry) >= 0; + case CELIX_FILTER_OPERAND_LESS: + return celix_filter_compareAttributeValue(filter, entry) < 0; + default: + assert(filter->operand == CELIX_FILTER_OPERAND_LESSEQUAL); + return celix_filter_compareAttributeValue(filter, entry) <= 0; } +} - switch (filter->operand) { - case CELIX_FILTER_OPERAND_SUBSTRING: - return filter_compareSubString(filter, entry); - case CELIX_FILTER_OPERAND_APPROX: - return strcasecmp(entry->value, filter->value) == 0; - case CELIX_FILTER_OPERAND_EQUAL: - return celix_filter_compareAttributeValue(filter, entry) == 0; - case CELIX_FILTER_OPERAND_GREATER: - return celix_filter_compareAttributeValue(filter, entry) > 0; - case CELIX_FILTER_OPERAND_GREATEREQUAL: - return celix_filter_compareAttributeValue(filter, entry) >= 0; - case CELIX_FILTER_OPERAND_LESS: - return celix_filter_compareAttributeValue(filter, entry) < 0; - case CELIX_FILTER_OPERAND_LESSEQUAL: - return celix_filter_compareAttributeValue(filter, entry) <= 0; - case CELIX_FILTER_OPERAND_AND: - case CELIX_FILTER_OPERAND_NOT: - case CELIX_FILTER_OPERAND_OR: - case CELIX_FILTER_OPERAND_PRESENT: - default: - //LCOV EXCL START - return false; //should not happen - //LCOV EXCL STOP - } -} - -celix_status_t filter_getString(celix_filter_t * filter, const char **filterStr) { +celix_status_t filter_getString(celix_filter_t* filter, const char** filterStr) { if (filter != NULL) { *filterStr = filter->filterStr; } return CELIX_SUCCESS; } -celix_status_t filter_match_filter(celix_filter_t *src, celix_filter_t *dest, bool *out) { +celix_status_t filter_match_filter(celix_filter_t* src, celix_filter_t* dest, bool* out) { bool result = celix_filter_equals(src, dest); if (out != NULL) { *out = result; @@ -651,8 +721,11 @@ celix_status_t filter_match_filter(celix_filter_t *src, celix_filter_t *dest, bo return CELIX_SUCCESS; } +celix_filter_t* celix_filter_create(const char* filterString) { + if (!filterString || strnlen(filterString, 1) == 0) { + filterString = "(|)"; + } -celix_filter_t* celix_filter_create(const char *filterString) { celix_autofree char* str = celix_utils_strdup(filterString); if (!str) { celix_err_push("Failed to create filter string"); @@ -660,13 +733,13 @@ celix_filter_t* celix_filter_create(const char *filterString) { } int pos = 0; - celix_autoptr(celix_filter_t) filter = filter_parseFilter(str, &pos); + celix_autoptr(celix_filter_t) filter = celix_filter_parseFilter(str, &pos); if (!filter) { celix_err_push("Failed to parse filter string"); return NULL; } if (pos != strlen(filterString)) { - celix_err_push("Filter Error: Extraneous trailing characters.\n"); + celix_err_push("Filter Error: Extraneous trailing characters."); return NULL; } @@ -674,14 +747,14 @@ celix_filter_t* celix_filter_create(const char *filterString) { filter->operand != CELIX_FILTER_OPERAND_NOT && filter->operand != CELIX_FILTER_OPERAND_SUBSTRING && filter->operand != CELIX_FILTER_OPERAND_PRESENT) { if (filter->attribute == NULL || filter->value == NULL) { - celix_err_push("Filter Error: Missing attribute or value.\n"); + celix_err_push("Filter Error: Missing attribute or value."); return NULL; } } filter->filterStr = celix_steal_ptr(str); if (celix_filter_compile(filter) != CELIX_SUCCESS) { - // TODO test error + celix_err_push("Failed to compile filter"); return NULL; } return celix_steal_ptr(filter); @@ -725,24 +798,27 @@ void celix_filter_destroy(celix_filter_t* filter) { } bool celix_filter_match(const celix_filter_t* filter, const celix_properties_t* properties) { - if (filter == NULL) { - return true; // matching on null(empty) filter is always true + if (!filter || !properties) { + return false; // matching on null filter or property is always false } - switch (filter->operand) { - case CELIX_FILTER_OPERAND_AND: { - celix_array_list_t* childern = filter->children; - for (int i = 0; i < celix_arrayList_size(childern); i++) { - celix_filter_t* childFilter = (celix_filter_t*)celix_arrayList_get(childern, i); + if (filter->operand == CELIX_FILTER_OPERAND_PRESENT) { + return celix_properties_get(properties, filter->attribute, NULL) != NULL; + } else if (filter->operand == CELIX_FILTER_OPERAND_AND) { + celix_array_list_t* children = filter->children; + for (int i = 0; i < celix_arrayList_size(children); i++) { + celix_filter_t* childFilter = (celix_filter_t*)celix_arrayList_get(children, i); bool childResult = celix_filter_match(childFilter, properties); if (!childResult) { return false; } } return true; - } - case CELIX_FILTER_OPERAND_OR: { + } else if (filter->operand == CELIX_FILTER_OPERAND_OR) { celix_array_list_t* children = filter->children; + if (celix_arrayList_size(children) == 0) { + return true; + } for (int i = 0; i < celix_arrayList_size(children); i++) { celix_filter_t* childFilter = (celix_filter_t*)celix_arrayList_get(children, i); bool childResult = celix_filter_match(childFilter, properties); @@ -751,35 +827,21 @@ bool celix_filter_match(const celix_filter_t* filter, const celix_properties_t* } } return false; - } - case CELIX_FILTER_OPERAND_NOT: { + } else if (filter->operand == CELIX_FILTER_OPERAND_NOT) { celix_filter_t* childFilter = celix_arrayList_get(filter->children, 0); bool childResult = celix_filter_match(childFilter, properties); return !childResult; } - case CELIX_FILTER_OPERAND_PRESENT: { - char* value = (properties == NULL) ? NULL : (char*)celix_properties_get(properties, filter->attribute, NULL); - return value != NULL; - } - case CELIX_FILTER_OPERAND_SUBSTRING: - case CELIX_FILTER_OPERAND_EQUAL: - case CELIX_FILTER_OPERAND_GREATER: - case CELIX_FILTER_OPERAND_GREATEREQUAL: - case CELIX_FILTER_OPERAND_LESS: - case CELIX_FILTER_OPERAND_LESSEQUAL: - case CELIX_FILTER_OPERAND_APPROX: { - const celix_properties_entry_t* entry = celix_properties_getEntry(properties, filter->attribute); - bool result = celix_filter_matchPropertyEntry(filter, entry); - return result; - } - } - // LCOV_EXCL_START - return false;// should not happen - // LCOV_EXCL_STOP + // substring, equal, greater, greaterEqual, less, lessEqual, approx done with matchPropertyEntry + const celix_properties_entry_t* entry = celix_properties_getEntry(properties, filter->attribute); + if (!entry) { + return false; + } + return celix_filter_matchPropertyEntry(filter, entry); } -bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *filter2) { +bool celix_filter_equals(const celix_filter_t* filter1, const celix_filter_t* filter2) { if (filter1 == filter2) { return true; } @@ -790,8 +852,8 @@ bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *fi return false; } - - if (filter1->operand == CELIX_FILTER_OPERAND_AND || filter1->operand == CELIX_FILTER_OPERAND_OR || filter1->operand == CELIX_FILTER_OPERAND_NOT) { + if (filter1->operand == CELIX_FILTER_OPERAND_AND || filter1->operand == CELIX_FILTER_OPERAND_OR || + filter1->operand == CELIX_FILTER_OPERAND_NOT) { assert(filter1->children != NULL); assert(filter2->children != NULL); int sizeSrc = celix_arrayList_size(filter1->children); @@ -800,11 +862,11 @@ bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *fi int i; int k; int sameCount = 0; - for (i =0; i < sizeSrc; ++i) { + for (i = 0; i < sizeSrc; ++i) { bool same = false; - celix_filter_t *srcPart = celix_arrayList_get(filter1->children, i); + celix_filter_t* srcPart = celix_arrayList_get(filter1->children, i); for (k = 0; k < sizeDest; ++k) { - celix_filter_t *destPart = celix_arrayList_get(filter2->children, k); + celix_filter_t* destPart = celix_arrayList_get(filter2->children, k); filter_match_filter(srcPart, destPart, &same); if (same) { sameCount += 1; @@ -817,7 +879,7 @@ bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *fi return false; } - //compare attr and value + // compare attr and value bool attrSame = false; bool valSame = false; if (filter1->attribute == NULL && filter2->attribute == NULL) { @@ -826,7 +888,7 @@ bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *fi attrSame = celix_utils_stringEquals(filter1->attribute, filter2->attribute); } - if (filter1->value == NULL && filter2->value == NULL) { + if (filter1->value == NULL && filter2->value == NULL) { valSame = true; } else if (filter1->value != NULL && filter2->value != NULL) { valSame = celix_utils_stringEquals(filter1->value, filter2->value); @@ -835,20 +897,21 @@ bool celix_filter_equals(const celix_filter_t *filter1, const celix_filter_t *fi return attrSame && valSame; } -const char* celix_filter_getFilterString(const celix_filter_t *filter) { +const char* celix_filter_getFilterString(const celix_filter_t* filter) { if (filter != NULL) { return filter->filterStr; } return NULL; } -const char* celix_filter_findAttribute(const celix_filter_t *filter, const char *attribute) { - const char *result = NULL; +const char* celix_filter_findAttribute(const celix_filter_t* filter, const char* attribute) { + const char* result = NULL; if (filter != NULL && attribute != NULL) { - if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || filter->operand == CELIX_FILTER_OPERAND_NOT) { + if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || + filter->operand == CELIX_FILTER_OPERAND_NOT) { int size = celix_arrayList_size(filter->children); for (int i = 0; i < size; ++i) { - celix_filter_t *child = celix_arrayList_get(filter->children, i); + celix_filter_t* child = celix_arrayList_get(filter->children, i); result = celix_filter_findAttribute(child, attribute); if (result != NULL) { break; @@ -861,21 +924,22 @@ const char* celix_filter_findAttribute(const celix_filter_t *filter, const char return result; } -static bool hasMandatoryEqualsValueAttribute(const celix_filter_t *filter, const char *attribute, bool negated, bool optional) { +static bool +hasMandatoryEqualsValueAttribute(const celix_filter_t* filter, const char* attribute, bool negated, bool optional) { bool equalsValueAttribute = false; if (filter != NULL && attribute != NULL) { - if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || filter->operand == CELIX_FILTER_OPERAND_NOT) { + if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || + filter->operand == CELIX_FILTER_OPERAND_NOT) { int size = celix_arrayList_size(filter->children); for (int i = 0; i < size; ++i) { - if (filter->operand == CELIX_FILTER_OPERAND_NOT) { negated = !negated; } else if (filter->operand == CELIX_FILTER_OPERAND_OR) { optional = true; } - celix_filter_t *child = celix_arrayList_get(filter->children, i); + celix_filter_t* child = celix_arrayList_get(filter->children, i); equalsValueAttribute = hasMandatoryEqualsValueAttribute(child, attribute, negated, optional); @@ -891,25 +955,26 @@ static bool hasMandatoryEqualsValueAttribute(const celix_filter_t *filter, const return equalsValueAttribute; } -bool celix_filter_hasMandatoryEqualsValueAttribute(const celix_filter_t *filter, const char *attribute) { +bool celix_filter_hasMandatoryEqualsValueAttribute(const celix_filter_t* filter, const char* attribute) { return hasMandatoryEqualsValueAttribute(filter, attribute, false, false); } -static bool hasMandatoryNegatedPresenceAttribute(const celix_filter_t *filter, const char *attribute, bool negated, bool optional) { +static bool +hasMandatoryNegatedPresenceAttribute(const celix_filter_t* filter, const char* attribute, bool negated, bool optional) { bool negatedPresenceAttribute = false; if (filter != NULL && attribute != NULL) { - if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || filter->operand == CELIX_FILTER_OPERAND_NOT) { + if (filter->operand == CELIX_FILTER_OPERAND_AND || filter->operand == CELIX_FILTER_OPERAND_OR || + filter->operand == CELIX_FILTER_OPERAND_NOT) { int size = celix_arrayList_size(filter->children); for (int i = 0; i < size; ++i) { - if (filter->operand == CELIX_FILTER_OPERAND_NOT) { negated = !negated; } else if (filter->operand == CELIX_FILTER_OPERAND_OR) { optional = true; } - celix_filter_t *child = celix_arrayList_get(filter->children, i); + celix_filter_t* child = celix_arrayList_get(filter->children, i); negatedPresenceAttribute = hasMandatoryNegatedPresenceAttribute(child, attribute, negated, optional); @@ -924,8 +989,8 @@ static bool hasMandatoryNegatedPresenceAttribute(const celix_filter_t *filter, c return negatedPresenceAttribute; } -bool celix_filter_hasMandatoryNegatedPresenceAttribute(const celix_filter_t *filter, const char *attribute) { +bool celix_filter_hasMandatoryNegatedPresenceAttribute(const celix_filter_t* filter, const char* attribute) { return hasMandatoryNegatedPresenceAttribute(filter, attribute, false, false); } -//NOLINTEND(misc-no-recursion) +// NOLINTEND(misc-no-recursion)
