This is an automated email from the ASF dual-hosted git repository. pnoltes pushed a commit to branch feature/674-improve-properties in repository https://gitbox.apache.org/repos/asf/celix.git
commit 968cc8ae7ddaad1f570e78deba5755d6f0d008ae Author: Pepijn Noltes <[email protected]> AuthorDate: Sun Jan 7 20:41:50 2024 +0100 Add support for filter match with an array property set entry --- libs/utils/gtest/src/CxxFilterTestSuite.cc | 82 ++++----- libs/utils/gtest/src/FilterTestSuite.cc | 113 +++++++++++++ libs/utils/include/celix_filter.h | 44 ++++- libs/utils/src/filter.c | 263 ++++++++++++++++++----------- 4 files changed, 359 insertions(+), 143 deletions(-) diff --git a/libs/utils/gtest/src/CxxFilterTestSuite.cc b/libs/utils/gtest/src/CxxFilterTestSuite.cc index 549eabba..8369883a 100644 --- a/libs/utils/gtest/src/CxxFilterTestSuite.cc +++ b/libs/utils/gtest/src/CxxFilterTestSuite.cc @@ -113,55 +113,47 @@ TEST_F(CxxFilterTestSuite, HasMandatoryNegatedPresenceAttribute) { } TEST_F(CxxFilterTestSuite, FilterForLongAttribute) { - celix::Filter filter1{"(key1>=10)"}; - EXPECT_TRUE(filter1.match({{"key1", "12"}})); - EXPECT_TRUE(filter1.match({{"key1", "11.1"}})); - EXPECT_TRUE(filter1.match({{"key1", "10"}})); - EXPECT_FALSE(filter1.match({{"key1", "2"}})); - EXPECT_FALSE(filter1.match({{"key1", "1"}})); - EXPECT_FALSE(filter1.match({{"key1", "0.1"}})); - - celix::Filter filter2{"(key1>20)"}; - EXPECT_TRUE(filter2.match({{"key1", "21"}})); - EXPECT_TRUE(filter2.match({{"key1", "20.1"}})); - EXPECT_FALSE(filter2.match({{"key1", "20"}})); - EXPECT_FALSE(filter2.match({{"key1", "3"}})); - EXPECT_FALSE(filter2.match({{"key1", "2"}})); - EXPECT_FALSE(filter2.match({{"key1", "0.1"}})); + celix::Properties props{}; + props.set("key1", 1L); + props.set("key2", 20L); + + EXPECT_EQ(celix::Properties::ValueType::Long, props.getType("key1")); + EXPECT_EQ(celix::Properties::ValueType::Long, props.getType("key2")); + + celix::Filter filter1{"(key1>=1)"}; + EXPECT_TRUE(filter1.match(props)); + celix::Filter filter2{"(key2<=3)"}; + EXPECT_FALSE(filter2.match(props)); + celix::Filter filter3{"(key2>=1)"}; + EXPECT_TRUE(filter3.match(props)); } TEST_F(CxxFilterTestSuite, FilterForDoubleAttribute) { - celix::Filter filter1{"(key1>=10.5)"}; - EXPECT_TRUE(filter1.match({{"key1", "12"}})); - EXPECT_TRUE(filter1.match({{"key1", "11.1"}})); - EXPECT_TRUE(filter1.match({{"key1", "10.5"}})); - EXPECT_FALSE(filter1.match({{"key1", "2"}})); - EXPECT_FALSE(filter1.match({{"key1", "1"}})); - EXPECT_FALSE(filter1.match({{"key1", "0.1"}})); - - celix::Filter filter2{"(key1>20.5)"}; - EXPECT_TRUE(filter2.match({{"key1", "21"}})); - EXPECT_TRUE(filter2.match({{"key1", "20.7"}})); - EXPECT_FALSE(filter2.match({{"key1", "20.5"}})); - EXPECT_FALSE(filter2.match({{"key1", "3"}})); - EXPECT_FALSE(filter2.match({{"key1", "2"}})); - EXPECT_FALSE(filter2.match({{"key1", "0.1"}})); + celix::Properties props{}; + props.set("key1", 1.1); + props.set("key2", 20.2); + + EXPECT_EQ(celix::Properties::ValueType::Double, props.getType("key1")); + EXPECT_EQ(celix::Properties::ValueType::Double, props.getType("key2")); + + celix::Filter filter1{"(key1>=1.1)"}; + EXPECT_TRUE(filter1.match(props)); + celix::Filter filter2{"(key2<=3.3)"}; + EXPECT_FALSE(filter2.match(props)); } TEST_F(CxxFilterTestSuite, FilterForVersionAttribute) { - celix::Filter filter1{"(key1>=1.2.3.qualifier)"}; - EXPECT_TRUE(filter1.match({{"key1", "1.2.3.qualifier"}})); - EXPECT_TRUE(filter1.match({{"key1", "2"}})); - EXPECT_TRUE(filter1.match({{"key1", "2.0.0"}})); - EXPECT_TRUE(filter1.match({{"key1", "1.2.4"}})); - EXPECT_FALSE(filter1.match({{"key1", "1.2.2"}})); - EXPECT_FALSE(filter1.match({{"key1", "1.0.0"}})); - EXPECT_FALSE(filter1.match({{"key1", "0.1"}})); - - celix::Filter filter2{"(key1>2.3.4)"}; - EXPECT_TRUE(filter2.match({{"key1", "3"}})); - EXPECT_TRUE(filter2.match({{"key1", "3.0.0"}})); - EXPECT_TRUE(filter2.match({{"key1", "2.3.5"}})); - EXPECT_FALSE(filter2.match({{"key1", "2.3.4"}})); - EXPECT_FALSE(filter2.match({{"key1", "0.0.3"}})); + celix::Properties props{}; + props.set("key1", celix::Version{1, 2, 3}); + props.set("key2", celix::Version{2, 0, 0}); + + EXPECT_EQ(celix::Properties::ValueType::Version, props.getType("key1")); + EXPECT_EQ(celix::Properties::ValueType::Version, props.getType("key2")); + + celix::Filter filter1{"(key1>=1.2.3)"}; + EXPECT_TRUE(filter1.match(props)); + celix::Filter filter2{"(key2<=2.0.0)"}; + EXPECT_TRUE(filter2.match(props)); + celix::Filter filter3{"(key2<=1.2.3)"}; + EXPECT_FALSE(filter3.match(props)); } diff --git a/libs/utils/gtest/src/FilterTestSuite.cc b/libs/utils/gtest/src/FilterTestSuite.cc index 22903cd1..8b770830 100644 --- a/libs/utils/gtest/src/FilterTestSuite.cc +++ b/libs/utils/gtest/src/FilterTestSuite.cc @@ -589,6 +589,119 @@ TEST_F(FilterTestSuite, InvalidEscapeTest) { EXPECT_EQ(nullptr, celix_filter_create("(\\")); //escape without following char } +TEST_F(FilterTestSuite, UnmatchedTypeMatchTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, "str", "20"); + celix_properties_setLong(props, "long", 20); + + celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(str<3)"); + EXPECT_TRUE(filter1 != nullptr); //note string is compared as string + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(long<3)"); + EXPECT_TRUE(filter2 != nullptr); + EXPECT_FALSE(celix_filter_match(filter2, props)); //note long is compared as long +} + +TEST_F(FilterTestSuite, MatchArrayTypesTest) { + const char* strings[] = {"a", "b", "c"}; + const long longs[] = {1, 2, 3}; + const double doubles[] = {1.0, 2.0, 3.0}; + const bool bools[] = {true, true, true}; + celix_autoptr(celix_version_t) v1 = celix_version_createVersionFromString("1.0.0"); + celix_autoptr(celix_version_t) v2 = celix_version_createVersionFromString("2.0.0"); + celix_autoptr(celix_version_t) v3 = celix_version_createVersionFromString("3.0.0"); + const celix_version_t* versions[] = {v1, v2, v3}; + + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setStrings(props, "strings", strings, 3); + celix_properties_setLongs(props, "longs", longs, 3); + celix_properties_setDoubles(props, "doubles", doubles, 3); + celix_properties_setBooleans(props, "bools", bools, 3); + celix_properties_setVersions(props, "versions", versions, 3); + + // Check if match is true if any of the array elements match + celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings=a)"); + EXPECT_TRUE(filter1 != nullptr); + EXPECT_TRUE(celix_filter_match(filter1, props)); + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(longs<2)"); + EXPECT_TRUE(filter2 != nullptr); + EXPECT_TRUE(celix_filter_match(filter2, props)); + + celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(doubles>2.9)"); + EXPECT_TRUE(filter3 != nullptr); + EXPECT_TRUE(celix_filter_match(filter3, props)); + + celix_autoptr(celix_filter_t) filter4 = celix_filter_create("(bools=true)"); + EXPECT_TRUE(filter4 != nullptr); + EXPECT_TRUE(celix_filter_match(filter4, props)); + + celix_autoptr(celix_filter_t) filter5 = celix_filter_create("(&(versions>=2.0.0)(versions<=2.0.0))"); + EXPECT_TRUE(filter5 != nullptr); + EXPECT_TRUE(celix_filter_match(filter5, props)); + + // Check if match is false if none of the array elements match + celix_autoptr(celix_filter_t) filter6 = celix_filter_create("(strings=x)"); + EXPECT_TRUE(filter6 != nullptr); + EXPECT_FALSE(celix_filter_match(filter6, props)); + + celix_autoptr(celix_filter_t) filter7 = celix_filter_create("(longs>3)"); + EXPECT_TRUE(filter7 != nullptr); + EXPECT_FALSE(celix_filter_match(filter7, props)); + + celix_autoptr(celix_filter_t) filter8 = celix_filter_create("(doubles<0.9)"); + EXPECT_TRUE(filter8 != nullptr); + EXPECT_FALSE(celix_filter_match(filter8, props)); + + celix_autoptr(celix_filter_t) filter9 = celix_filter_create("(bools=false)"); + EXPECT_TRUE(filter9 != nullptr); + EXPECT_FALSE(celix_filter_match(filter9, props)); + + celix_autoptr(celix_filter_t) filter10 = celix_filter_create("(&(versions>=4.0.0)(versions<=4.0.0))"); + EXPECT_TRUE(filter10 != nullptr); + EXPECT_FALSE(celix_filter_match(filter10, props)); +} + +TEST_F(FilterTestSuite, ApproxWithArrayAttributesTest) { + const char* strings[] = {"abcdef", "defghi", "ghijkl"}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setStrings(props, "strings", strings, 3); + + celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings~=abc)"); + EXPECT_TRUE(filter1 != nullptr); + EXPECT_TRUE(celix_filter_match(filter1, props)); + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(strings~=def)"); + EXPECT_TRUE(filter2 != nullptr); + EXPECT_TRUE(celix_filter_match(filter2, props)); + + celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(strings~=jkl)"); + EXPECT_TRUE(filter3 != nullptr); + EXPECT_TRUE(celix_filter_match(filter3, props)); + + celix_autoptr(celix_filter_t) filter4 = celix_filter_create("(strings~=mno)"); + EXPECT_TRUE(filter4 != nullptr); + EXPECT_FALSE(celix_filter_match(filter4, props)); +} + +TEST_F(FilterTestSuite, SubStringWithArrayAttributesTest) { + const char* strings[] = {"John Doe", "Jane Doe", "John Smith"}; + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_setStrings(props, "strings", strings, 3); + + celix_autoptr(celix_filter_t) filter1 = celix_filter_create("(strings=John*)"); + EXPECT_TRUE(filter1 != nullptr); + EXPECT_TRUE(celix_filter_match(filter1, props)); + + celix_autoptr(celix_filter_t) filter2 = celix_filter_create("(strings=*Doe)"); + EXPECT_TRUE(filter2 != nullptr); + EXPECT_TRUE(celix_filter_match(filter2, props)); + + // check if match is false if none of the array elements match with a substring + celix_autoptr(celix_filter_t) filter3 = celix_filter_create("(strings=*Johnson)"); + EXPECT_TRUE(filter3 != nullptr); + EXPECT_FALSE(celix_filter_match(filter3, props)); +} #include "filter.h" TEST_F(FilterTestSuite, DeprecatedApiTest) { diff --git a/libs/utils/include/celix_filter.h b/libs/utils/include/celix_filter.h index bb2ed4ea..fcb4c3d0 100644 --- a/libs/utils/include/celix_filter.h +++ b/libs/utils/include/celix_filter.h @@ -21,6 +21,7 @@ * @file celix_filter.h * @brief Header file for the Celix Filter API. * + * #Introduction * An Apache Celix filter is a based on LDAP filters, for more information about LDAP filters see: * - https://tools.ietf.org/html/rfc4515 * - https://tools.ietf.org/html/rfc1960 @@ -54,10 +55,47 @@ * Apache Celix filters can be used to match a set of Apache Celix properties and such Apache Celix filters should be * used together with a set of properties. * - * Internally attribute values will be parsed to a long, double, boolean and Apache Celix version if - * possible during creation. And these typed attribute values will be used in the to-be-matched property value, - * if the property value is of the same type. + * #Filter matching and property value types + * When matching a filter the attribute type of a filter node is used to determine the type of the property value and + * this type is used to compare the property value with the filter attribute value. + * If the property value is of a type that the filter attribute value can be parsed to, the filter attribute value the + * comparison is done with the matching type. If the property value is not of a type that the filter attribute value + * can be parsed to, the comparison is done with the string representation of the property value. * + * Internally attribute values will be parsed to a long, double, boolean, Apache Celix version and array list of + * longs, doubles, booleans, Apache Celix versions and string if possible during creation. + * And these typed attribute values will be used in the to-be-matched property value. + * + * Example: The filter "(key>20)" and a property set with a long value 3 for key "key", will match and the same + * filter but with a property set which has a string value "3" for key "key", will not match. + * + * #Filter matching and property value arrays + * If a filter matches a property set entry which has an array value (either a long, boolean, double, string or version + * array) the filter match will check if the array contains a single entry which matches the filter attribute value using + * the aforementioned "Filter matching and property value types" rules. + * + * Filter matching with array is supported for the following operands: "=" (including substring), ">=", "<=", ">", "<" + * and "~=". + * + * Example: The filter "(key=3)" will match the property set entry with key "key" and a long array list value of + * [1,2,3]. + * + * #Substring filter operand + * The substring filter operand is used to match a string value with a filter attribute value. The filter attribute + * value can contain a `*` to match any character sequence. The filter attribute value can also contain a `*` at the + * start or end of the string to match any character sequence at the start or end of the string. + * + * A substring filter operand uses the string representation of the property value to match with the filter attribute + * or if the property value is an string array the substring filter operand will match if any of the string array values. + * + * Example: The filter "(key=*Smith)" will match the property set entry with key "key" and a string value "John Smith". + * + * #Approx filter operand + * The approx filter operand is used to check if the filter attribute value is a substring of the property value. + * A approx filter operand uses the string representation of the property value to match with the filter attribute or + * if the property value is an string array the approx filter operand will match if any of the string array values. + * + * Example: The filter "(key~=Smith)" will match the property set entry with key "key" and a string value "John Smith". */ #ifndef CELIX_FILTER_H_ diff --git a/libs/utils/src/filter.c b/libs/utils/src/filter.c index bad00bab..601ae499 100644 --- a/libs/utils/src/filter.c +++ b/libs/utils/src/filter.c @@ -515,40 +515,38 @@ static bool celix_filter_hasFilterChildren(celix_filter_t* filter) { filter->operand == CELIX_FILTER_OPERAND_NOT; } +static void celix_filter_destroyInternal(celix_filter_internal_t* internal) { + if (internal) { + celix_version_destroy(internal->versionValue); + free(internal); + } +} + +CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_filter_internal_t, celix_filter_destroyInternal) + /** * Compiles the filter, so that the attribute values are converted to the typed values if possible. */ static celix_status_t celix_filter_compile(celix_filter_t* filter) { if (celix_filter_isCompareOperand(filter->operand)) { - filter->internal = calloc(1, sizeof(*filter->internal)); - if (filter->internal == NULL) { - celix_err_push("Filter Error: Failed to allocate memory."); - return CELIX_ENOMEM; + celix_autoptr(celix_filter_internal_t) internal = calloc(1, sizeof(*internal)); + if (!internal) { + return ENOMEM; } - do { - filter->internal->longValue = - celix_utils_convertStringToLong(filter->value, 0, &filter->internal->convertedToLong); - if (filter->internal->convertedToLong) { - break; - } - filter->internal->doubleValue = - celix_utils_convertStringToDouble(filter->value, 0.0, &filter->internal->convertedToDouble); - if (filter->internal->convertedToDouble) { - break; - } - filter->internal->boolValue = - celix_utils_convertStringToBool(filter->value, false, &filter->internal->convertedToBool); - if (filter->internal->convertedToBool) { - break; - } - celix_version_t* version; - celix_status_t convertStatus = celix_utils_convertStringToVersion(filter->value, NULL, &version); - if (convertStatus == CELIX_SUCCESS) { - filter->internal->versionValue = version; - filter->internal->convertedToVersion = true; - break; - } - } while(false); + internal->longValue = + celix_utils_convertStringToLong(filter->value, 0, &internal->convertedToLong); + internal->doubleValue = + celix_utils_convertStringToDouble(filter->value, 0.0, &internal->convertedToDouble); + internal->boolValue = + celix_utils_convertStringToBool(filter->value, false, &internal->convertedToBool); + + celix_status_t convertStatus = celix_utils_convertStringToVersion(filter->value, NULL, &internal->versionValue); + if (convertStatus == ENOMEM) { + return ENOMEM; + } + internal->convertedToVersion = convertStatus == CELIX_SUCCESS; + + filter->internal = celix_steal_ptr(internal); } if (celix_filter_hasFilterChildren(filter)) { @@ -602,68 +600,96 @@ static int celix_filter_cmpBool(bool a, bool b) { } } -static int celix_filter_compareAttributeValue(const celix_filter_t* filter, const celix_properties_entry_t* entry) { - // not converted, fallback on string compare - if (!filter->internal->convertedToLong && !filter->internal->convertedToDouble && - !filter->internal->convertedToBool && !filter->internal->convertedToVersion) { - return strcmp(entry->value, filter->value); +static bool celix_utils_convertCompareToBool(enum celix_filter_operand_enum op, int cmp) { + switch (op) { + case CELIX_FILTER_OPERAND_EQUAL: + return cmp == 0; + case CELIX_FILTER_OPERAND_GREATER: + return cmp > 0; + case CELIX_FILTER_OPERAND_GREATEREQUAL: + return cmp >= 0; + case CELIX_FILTER_OPERAND_LESS: + return cmp < 0; + case CELIX_FILTER_OPERAND_LESSEQUAL: + return cmp <= 0; + default: + //LCOV_EXCL_START + assert(false); + return false; + //LCOV_EXCL_STOP } +} - // 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) { - return celix_filter_cmpDouble(entry->typed.doubleValue, filter->internal->doubleValue); - } else if (filter->internal->convertedToBool && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { - return celix_filter_cmpBool(entry->typed.boolValue, filter->internal->boolValue); - } else if (filter->internal->convertedToVersion && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - return celix_version_compareTo(entry->typed.versionValue, filter->internal->versionValue); +static bool celix_utils_matchLongArrays(enum celix_filter_operand_enum op, const celix_array_list_t* list, long attributeValue) { + assert(list != NULL); + for (int i = 0 ; i < celix_arrayList_size(list); ++i) { + int cmp = celix_filter_cmpLong(celix_arrayList_getLong(list, i), attributeValue); + if (celix_utils_convertCompareToBool(op, cmp)) { + return true; + } } + return false; +} - // 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); - if (propertyConverted) { - return celix_filter_cmpLong(val, filter->internal->longValue); - } - } else if (filter->internal->convertedToDouble) { - double val = celix_utils_convertStringToDouble(entry->value, 0.0, &propertyConverted); - if (propertyConverted) { - return celix_filter_cmpDouble(val, filter->internal->doubleValue); +static bool celix_utils_matchDoubleArrays(enum celix_filter_operand_enum op, const celix_array_list_t* list, double attributeValue) { + assert(list != NULL); + for (int i = 0 ; i < celix_arrayList_size(list); ++i) { + int cmp = celix_filter_cmpDouble(celix_arrayList_getDouble(list, i), attributeValue); + if (celix_utils_convertCompareToBool(op, cmp)) { + return true; } - } else if (filter->internal->convertedToBool) { - bool val = celix_utils_convertStringToBool(entry->value, false, &propertyConverted); - if (propertyConverted) { - return celix_filter_cmpBool(val, filter->internal->boolValue); + } + return false; +} + + +static bool celix_utils_matchBoolArrays(enum celix_filter_operand_enum op, const celix_array_list_t* list, bool attributeValue) { + assert(list != NULL); + for (int i = 0 ; i < celix_arrayList_size(list); ++i) { + int cmp = celix_filter_cmpBool(celix_arrayList_getBool(list, i), attributeValue); + if (celix_utils_convertCompareToBool(op, cmp)) { + return true; } - } else if (filter->internal->convertedToVersion) { - celix_version_t* val; - celix_status_t convertStatus = celix_utils_convertStringToVersion(entry->value, NULL, &val); - if (convertStatus == CELIX_SUCCESS) { - int cmp = celix_version_compareTo(val, filter->internal->versionValue); - celix_version_destroy(val); - return cmp; + } + return false; +} + +static bool celix_utils_matchVersionArrays(enum celix_filter_operand_enum op, const celix_array_list_t* list, celix_version_t* attributeValue) { + assert(list != NULL); + for (int i = 0 ; i < celix_arrayList_size(list); ++i) { + int cmp = celix_version_compareTo((celix_version_t*)celix_arrayList_get(list, i), attributeValue); + if (celix_utils_convertCompareToBool(op, cmp)) { + return true; } } + return false; +} - // fallback on string compare - return strcmp(entry->value, filter->value); +static bool celix_utils_matchStringArrays(enum celix_filter_operand_enum op, const celix_array_list_t* list, const char* attributeValue) { + assert(list != NULL); + for (int i = 0 ; i < celix_arrayList_size(list); ++i) { + int cmp = strcmp(celix_arrayList_getString(list, i), attributeValue); + if (celix_utils_convertCompareToBool(op, cmp)) { + return true; + } + } + return false; } -static bool celix_filter_matchSubString(const celix_filter_t* filter, const celix_properties_entry_t* entry) { + +static bool celix_filter_matchSubStringForValue(const celix_filter_t* filter, const char* value) { assert(filter->children && celix_arrayList_size(filter->children) >= 2); - size_t strLen = celix_utils_strlen(entry->value); + size_t strLen = celix_utils_strlen(value); const char* initial = celix_arrayList_get(filter->children, 0); const char* final = celix_arrayList_get(filter->children, celix_arrayList_size(filter->children) - 1); - const char* currentValue = entry->value; + const char* currentValue = value; if (initial) { - const char* found = strstr(entry->value, initial); + const char* found = strstr(value, initial); currentValue = found + celix_utils_strlen(initial); - if (!found || found != entry->value) { + if (!found || found != value) { return false; } } @@ -679,7 +705,7 @@ static bool celix_filter_matchSubString(const celix_filter_t* filter, const celi if (final) { const char* found = strstr(currentValue, final); - if (!found || found + celix_utils_strlen(final) != entry->value + strLen) { + if (!found || found + celix_utils_strlen(final) != value + strLen) { return false; } } @@ -687,24 +713,73 @@ static bool celix_filter_matchSubString(const celix_filter_t* filter, const celi return true; } +static bool celix_filter_matchSubString(const celix_filter_t* filter, const celix_properties_entry_t* entry) { + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING_ARRAY) { + for (int i = 0; i < celix_arrayList_size(entry->typed.arrayValue); i++) { + const char* substr = celix_arrayList_getString(entry->typed.arrayValue, i); + if (celix_filter_matchSubStringForValue(filter, substr)) { + return true; + } + return false; + } + } + return celix_filter_matchSubStringForValue(filter, entry->value); +} + +static bool celix_filter_matchApprox(const celix_filter_t* filter, const celix_properties_entry_t* entry) { + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING_ARRAY) { + for (int i = 0; i < celix_arrayList_size(entry->typed.arrayValue); i++) { + const char* substr = celix_arrayList_getString(entry->typed.arrayValue, i); + if (strcasestr(substr, filter->value) != NULL) { + return true; + } + } + return false; + } + return strcasestr(entry->value, filter->value) != NULL; +} + static bool celix_filter_matchPropertyEntry(const celix_filter_t* filter, const celix_properties_entry_t* entry) { - switch (filter->operand) { - case CELIX_FILTER_OPERAND_SUBSTRING: + if (filter->operand == 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; + } else if (filter->operand == CELIX_FILTER_OPERAND_APPROX) { + return celix_filter_matchApprox(filter, entry); + } + + assert(filter->operand == CELIX_FILTER_OPERAND_EQUAL || filter->operand == CELIX_FILTER_OPERAND_GREATER || + filter->operand == CELIX_FILTER_OPERAND_LESS || filter->operand == CELIX_FILTER_OPERAND_GREATEREQUAL || + filter->operand == CELIX_FILTER_OPERAND_LESSEQUAL); + + + //match for array types + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG_ARRAY && filter->internal->convertedToLong) { + return celix_utils_matchLongArrays(filter->operand, entry->typed.arrayValue, filter->internal->longValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE_ARRAY && filter->internal->convertedToDouble) { + return celix_utils_matchDoubleArrays(filter->operand, entry->typed.arrayValue, filter->internal->doubleValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL_ARRAY && filter->internal->convertedToBool) { + return celix_utils_matchBoolArrays(filter->operand, entry->typed.arrayValue, filter->internal->boolValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION_ARRAY && filter->internal->convertedToVersion) { + return celix_utils_matchVersionArrays(filter->operand, entry->typed.arrayValue, filter->internal->versionValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING_ARRAY) { + return celix_utils_matchStringArrays(filter->operand, entry->typed.arrayValue, filter->value); + } + + //regular compare -> match + int cmp; + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG && filter->internal->convertedToLong) { + cmp = celix_filter_cmpLong(entry->typed.longValue, filter->internal->longValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE && filter->internal->convertedToDouble) { + cmp = celix_filter_cmpDouble(entry->typed.doubleValue, filter->internal->doubleValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL && filter->internal->convertedToBool) { + cmp = celix_filter_cmpBool(entry->typed.boolValue, filter->internal->boolValue); + } else if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION && filter->internal->convertedToVersion) { + cmp = celix_version_compareTo(entry->typed.versionValue, filter->internal->versionValue); + } else { + // type string or property type and converted filter attribute value do not match -> + // fallback on string compare + cmp = strcmp(entry->value, filter->value); } + return celix_utils_convertCompareToBool(filter->operand, cmp); } celix_status_t filter_getString(celix_filter_t* filter, const char** filterStr) { @@ -737,8 +812,9 @@ celix_filter_t* celix_filter_create(const char* filterString) { celix_err_push("Filter Error: Extraneous trailing characters."); return NULL; } - if (celix_filter_compile(filter) != CELIX_SUCCESS) { - celix_err_push("Failed to compile filter"); + celix_status_t compileStatus = celix_filter_compile(filter); + if (compileStatus != CELIX_SUCCESS) { + celix_err_pushf("Failed to compile filter: %s", celix_strerror(compileStatus)); return NULL; } filter->filterStr = celix_utils_strdup(filterString); @@ -764,10 +840,7 @@ void celix_filter_destroy(celix_filter_t* filter) { filter->attribute = NULL; free((char*)filter->filterStr); filter->filterStr = NULL; - if (filter->internal != NULL) { - celix_version_destroy(filter->internal->versionValue); - free(filter->internal); - } + celix_filter_destroyInternal(filter->internal); free(filter); } @@ -807,10 +880,10 @@ bool celix_filter_match(const celix_filter_t* filter, const celix_properties_t* return !childResult; } - // substring, equal, greater, greaterEqual, less, lessEqual, approx done with matchPropertyEntry + // present, 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 false; } return celix_filter_matchPropertyEntry(filter, entry); }
