From c28810e96780c9a82b6b4be728f731b8a1907a72 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 31 Dec 2022 16:58:58 +0100 Subject: [PATCH 01/63] Rename current properties imp to properties_v1.c --- libs/utils/CMakeLists.txt | 2 +- libs/utils/src/{properties.c => properties_v1.c} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename libs/utils/src/{properties.c => properties_v1.c} (100%) diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 51fa47ca1..e8f51369a 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -36,7 +36,7 @@ add_library(utils SHARED src/celix_threads.c src/version.c src/version_range.c - src/properties.c + src/properties_v1.c src/utils.c src/ip_utils.c src/filter.c diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties_v1.c similarity index 100% rename from libs/utils/src/properties.c rename to libs/utils/src/properties_v1.c From 1ff603ba0702464c928bd9c22475bf834628f85b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 1 Jan 2023 20:13:06 +0100 Subject: [PATCH 02/63] Refactor celix properties to be based on celix_string_hash_map. --- .../src/pubsub_discovery_impl.c | 2 +- .../src/endpoint_descriptor_writer.c | 13 +- .../src/remote_service_admin_dfi.c | 15 +- .../rsa_shm/src/rsa_shm_impl.c | 5 +- .../topology_manager/src/topology_manager.c | 3 +- .../topology_manager/tms_tst/tms_tests.cpp | 9 +- libs/framework/src/service_reference.c | 11 +- libs/utils/CMakeLists.txt | 10 +- libs/utils/gtest/CMakeLists.txt | 5 + .../resources}/properties.txt | 0 .../utils/gtest/src/CxxPropertiesTestSuite.cc | 16 + .../src/PropertiesTestSuite.cc} | 171 +++--- libs/utils/include/celix/Properties.h | 19 +- libs/utils/include/celix_long_hash_map.h | 4 + libs/utils/include/celix_properties.h | 243 +++++++- libs/utils/include/celix_string_hash_map.h | 5 + libs/utils/include_deprecated/properties.h | 7 +- libs/utils/src/properties.c | 523 ++++++++++++++++++ libs/utils/src/utils.c | 2 +- 19 files changed, 884 insertions(+), 179 deletions(-) rename libs/utils/{private/resources-test => gtest/resources}/properties.txt (100%) rename libs/utils/{private/test/properties_test.cpp => gtest/src/PropertiesTestSuite.cc} (62%) create mode 100644 libs/utils/src/properties.c diff --git a/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c b/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c index c6d1aa954..4815bd920 100644 --- a/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c +++ b/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c @@ -558,7 +558,7 @@ static char* pubsub_discovery_createJsonEndpoint(const celix_properties_t *props json_t *jsEndpoint = json_object(); const char* propKey = NULL; - PROPERTIES_FOR_EACH((celix_properties_t*)props, propKey) { + CELIX_PROPERTIES_FOR_EACH(props, propKey) { const char* val = celix_properties_get(props, propKey, NULL); json_object_set_new(jsEndpoint, propKey, json_string(val)); } diff --git a/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c b/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c index d2e2421fa..66580b5a6 100644 --- a/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c +++ b/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c @@ -139,15 +139,11 @@ static celix_status_t endpointDescriptorWriter_writeEndpoint(endpoint_descriptor } else { xmlTextWriterStartElement(writer->writer, ENDPOINT_DESCRIPTION); - hash_map_iterator_pt iter = hashMapIterator_create(endpoint->properties); - while (hashMapIterator_hasNext(iter)) { - hash_map_entry_pt entry = hashMapIterator_nextEntry(iter); - - void* propertyName = hashMapEntry_getKey(entry); - const xmlChar* propertyValue = (const xmlChar*) hashMapEntry_getValue(entry); - + const char* propertyName; + CELIX_PROPERTIES_FOR_EACH(endpoint->properties, propertyName) { + const xmlChar* propertyValue = (const xmlChar*) celix_properties_get(endpoint->properties, propertyName, ""); xmlTextWriterStartElement(writer->writer, PROPERTY); - xmlTextWriterWriteAttribute(writer->writer, NAME, propertyName); + xmlTextWriterWriteAttribute(writer->writer, NAME, (const xmlChar*)propertyName); if (strcmp(OSGI_FRAMEWORK_OBJECTCLASS, (char*) propertyName) == 0) { // objectClass *must* be represented as array of string values... @@ -162,7 +158,6 @@ static celix_status_t endpointDescriptorWriter_writeEndpoint(endpoint_descriptor xmlTextWriterEndElement(writer->writer); } - hashMapIterator_destroy(iter); xmlTextWriterEndElement(writer->writer); } diff --git a/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c b/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c index 31bddb24c..656b37734 100644 --- a/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c +++ b/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c @@ -723,10 +723,7 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic } } - hash_map_entry_pt entry = hashMap_getEntry(endpointProperties, (void *) OSGI_FRAMEWORK_SERVICE_ID); - - char* key = hashMapEntry_getKey(entry); - char *serviceId = (char *) hashMap_remove(endpointProperties, (void *) OSGI_FRAMEWORK_SERVICE_ID); + const char* serviceId = celix_properties_get(endpointProperties, CELIX_FRAMEWORK_SERVICE_ID, "-1"); const char *uuid = NULL; char buf[512]; @@ -750,12 +747,10 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic celix_properties_set(endpointProperties, RSA_DFI_ENDPOINT_URL, url); if (props != NULL) { - hash_map_iterator_pt propIter = hashMapIterator_create(props); - while (hashMapIterator_hasNext(propIter)) { - hash_map_entry_pt entry = hashMapIterator_nextEntry(propIter); - celix_properties_set(endpointProperties, (char*)hashMapEntry_getKey(entry), (char*)hashMapEntry_getValue(entry)); + const char* key; + CELIX_PROPERTIES_FOR_EACH(props, key) { + celix_properties_set(endpointProperties, key, celix_properties_get(props, key, "")); } - hashMapIterator_destroy(propIter); } *endpoint = calloc(1, sizeof(**endpoint)); @@ -769,8 +764,6 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic (*endpoint)->properties = endpointProperties; } - free(key); - free(serviceId); free(keys); return status; diff --git a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c index b72cb95db..2d8c2bc93 100755 --- a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c +++ b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c @@ -230,12 +230,12 @@ static void rsaShm_overlayProperties(celix_properties_t *additionalProperties, c * A property key in the additional properties map must therefore override any case variant property key in the properties of the specified Service Reference.*/ const char *additionalPropKey = NULL; const char *servicePropKey = NULL; - PROPERTIES_FOR_EACH(additionalProperties, additionalPropKey) { + CELIX_PROPERTIES_FOR_EACH(additionalProperties, additionalPropKey) { if (strcmp(additionalPropKey,(char*) OSGI_FRAMEWORK_OBJECTCLASS) != 0 && strcmp(additionalPropKey,(char*) OSGI_FRAMEWORK_SERVICE_ID) != 0) { bool propKeyCaseEqual = false; - PROPERTIES_FOR_EACH(serviceProperties, servicePropKey) { + CELIX_PROPERTIES_FOR_EACH(serviceProperties, servicePropKey) { if (strcasecmp(additionalPropKey,servicePropKey) == 0) { const char* val = celix_properties_get(additionalProperties,additionalPropKey,NULL); celix_properties_set(serviceProperties,servicePropKey,val); @@ -250,7 +250,6 @@ static void rsaShm_overlayProperties(celix_properties_t *additionalProperties, c } } } - return; } static bool rsaShm_isConfigTypeMatched(celix_properties_t *properties) { diff --git a/bundles/remote_services/topology_manager/src/topology_manager.c b/bundles/remote_services/topology_manager/src/topology_manager.c index f834ac525..691e72afc 100644 --- a/bundles/remote_services/topology_manager/src/topology_manager.c +++ b/bundles/remote_services/topology_manager/src/topology_manager.c @@ -402,8 +402,7 @@ celix_status_t topologyManager_importScopeChanged(void *handle, char *service_na hash_map_entry_pt entry = hashMapIterator_nextEntry(importedServicesIterator); endpoint = hashMapEntry_getKey(entry); - entry = hashMap_getEntry(endpoint->properties, (void *) OSGI_FRAMEWORK_OBJECTCLASS); - char* name = (char *) hashMapEntry_getValue(entry); + const char* name = celix_properties_get(endpoint->properties, OSGI_FRAMEWORK_OBJECTCLASS, ""); // Test if a service with the same name is imported if (strcmp(name, service_name) == 0) { found = true; diff --git a/bundles/remote_services/topology_manager/tms_tst/tms_tests.cpp b/bundles/remote_services/topology_manager/tms_tst/tms_tests.cpp index 92bc86b6e..72bfbd57f 100644 --- a/bundles/remote_services/topology_manager/tms_tst/tms_tests.cpp +++ b/bundles/remote_services/topology_manager/tms_tst/tms_tests.cpp @@ -397,8 +397,7 @@ extern "C" { for (unsigned int i = 0; i < arrayList_size(epList); i++) { endpoint_description_t *ep = (endpoint_description_t *) arrayList_get(epList, i); celix_properties_t *props = ep->properties; - hash_map_entry_pt entry = hashMap_getEntry(props, (void*)"key2"); - char* value = (char*) hashMapEntry_getValue(entry); + const char* value = celix_properties_get(props, "key2", ""); EXPECT_STREQ("inaetics", value); /* printf("Service: %s ", ep->service); @@ -433,8 +432,7 @@ extern "C" { for (unsigned int i = 0; i < arrayList_size(epList); i++) { endpoint_description_t *ep = (endpoint_description_t *) arrayList_get(epList, i); celix_properties_t *props = ep->properties; - hash_map_entry_pt entry = hashMap_getEntry(props, (void*)"key2"); - char* value = (char*) hashMapEntry_getValue(entry); + const char* value = celix_properties_get(props, "key2", ""); EXPECT_STREQ("inaetics", value); } printf("End: %s\n", __func__); @@ -458,8 +456,7 @@ extern "C" { for (unsigned int i = 0; i < arrayList_size(epList); i++) { endpoint_description_t *ep = (endpoint_description_t *) arrayList_get(epList, i); celix_properties_t *props = ep->properties; - hash_map_entry_pt entry = hashMap_getEntry(props, (void *)"key2"); - char* value = (char*) hashMapEntry_getValue(entry); + const char* value = celix_properties_get(props, "key2", ""); EXPECT_STREQ("inaetics", value); } printf("End: %s\n", __func__); diff --git a/libs/framework/src/service_reference.c b/libs/framework/src/service_reference.c index 183023514..a7faebd77 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -200,17 +200,14 @@ FRAMEWORK_EXPORT celix_status_t serviceReference_getPropertyKeys(service_referen properties_pt props = NULL; serviceRegistration_getProperties(ref->registration, &props); - hash_map_iterator_pt it; int i = 0; - int vsize = hashMap_size(props); + int vsize = celix_properties_size(props); *size = (unsigned int)vsize; *keys = malloc(vsize * sizeof(**keys)); - it = hashMapIterator_create(props); - while (hashMapIterator_hasNext(it)) { - (*keys)[i] = hashMapIterator_nextKey(it); - i++; + const char* key; + CELIX_PROPERTIES_FOR_EACH(props, key) { + (*keys)[i++] = (char*)key; } - hashMapIterator_destroy(it); return status; } diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index e8f51369a..6de893b6d 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -36,7 +36,7 @@ add_library(utils SHARED src/celix_threads.c src/version.c src/version_range.c - src/properties_v1.c + src/properties.c src/utils.c src/ip_utils.c src/filter.c @@ -114,10 +114,6 @@ if (ENABLE_TESTING) target_include_directories(linked_list_test PRIVATE include_deprecated) target_link_libraries(linked_list_test Celix::utils CppUTest::CppUTest pthread) - add_executable(properties_test private/test/properties_test.cpp) - target_include_directories(properties_test PRIVATE include_deprecated) - target_link_libraries(properties_test CppUTest::CppUTest CppUTest::CppUTestExt Celix::utils pthread) - add_executable(utils_test private/test/utils_test.cpp) target_include_directories(utils_test PRIVATE include_deprecated) target_link_libraries(utils_test CppUTest::CppUTest Celix::utils pthread) @@ -134,13 +130,10 @@ if (ENABLE_TESTING) target_include_directories(version_test PRIVATE include_deprecated) target_link_libraries(version_test CppUTest::CppUTest Celix::utils pthread) - configure_file(private/resources-test/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY) - add_test(NAME run_array_list_test COMMAND array_list_test) add_test(NAME run_hash_map_test COMMAND hash_map_test) add_test(NAME run_celix_threads_test COMMAND celix_threads_test) add_test(NAME run_linked_list_test COMMAND linked_list_test) - add_test(NAME run_properties_test COMMAND properties_test) add_test(NAME run_utils_test COMMAND utils_test) add_test(NAME run_ip_utils_test COMMAND ip_utils_test) add_test(NAME filter_test COMMAND filter_test) @@ -150,7 +143,6 @@ if (ENABLE_TESTING) setup_target_for_coverage(hash_map_test) setup_target_for_coverage(celix_threads_test) setup_target_for_coverage(linked_list_test) - setup_target_for_coverage(properties_test) setup_target_for_coverage(utils_test) setup_target_for_coverage(ip_utils_test) setup_target_for_coverage(filter_test) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 7440357cf..dee276f77 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(test_utils src/HashMapTestSuite.cc src/ArrayListTestSuite.cc src/FileUtilsTestSuite.cc + src/PropertiesTestSuite.cc ${CELIX_UTIL_TEST_SOURCES_FOR_CXX_HEADERS} ) @@ -37,6 +38,10 @@ target_link_libraries(test_utils PRIVATE Celix::utils GTest::gtest GTest::gtest_ target_include_directories(test_utils PRIVATE ../src) #for version_private (needs refactoring of test) celix_deprecated_utils_headers(test_utils) +####### Configure properties file for testing ######################################################################### +configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY) + + ####### generating zip file used for testing ########################################################################## file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/top.properties" CONTENT "level=1\n") file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/subdir/sub.properties" CONTENT "level=2\n") diff --git a/libs/utils/private/resources-test/properties.txt b/libs/utils/gtest/resources/properties.txt similarity index 100% rename from libs/utils/private/resources-test/properties.txt rename to libs/utils/gtest/resources/properties.txt diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index 01241b99a..d5acac775 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -59,6 +59,22 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) { EXPECT_EQ(5, count); } +TEST_F(CxxPropertiesTestSuite, testLoopForSize0And1) { + celix::Properties props0{}; + for (const auto& pair : props0) { + FAIL() << "Should not get an loop entry with a properties size of 0. got key: " << pair.first; + } + + celix::Properties props1{}; + props1.set("key1", "value1"); + int count = 0; + for (const auto& pair : props1) { + EXPECT_EQ(pair.first, "key1"); + count++; + } + EXPECT_EQ(1, count); +} + TEST_F(CxxPropertiesTestSuite, testCopy) { celix::Properties props{}; diff --git a/libs/utils/private/test/properties_test.cpp b/libs/utils/gtest/src/PropertiesTestSuite.cc similarity index 62% rename from libs/utils/private/test/properties_test.cpp rename to libs/utils/gtest/src/PropertiesTestSuite.cc index 613f6ef24..62bfa8dd1 100644 --- a/libs/utils/private/test/properties_test.cpp +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -16,74 +16,46 @@ * specific language governing permissions and limitations * under the License. */ -/** - * properties_test.cpp - * - * \date Feb 11, 2013 - * \author Apache Celix Project Team - * \copyright Apache License, Version 2.0 - */ - -#include -#include -#include "CppUTest/TestHarness.h" -#include "CppUTest/TestHarness_c.h" -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTestExt/MockSupport.h" +#include -extern "C" { -#include - -#include "properties.h" #include "celix_properties.h" -} -int main(int argc, char** argv) { - MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); - return RUN_ALL_TESTS(argc, argv); -} +using ::testing::MatchesRegex; -TEST_GROUP(properties) { - celix_properties_t *properties; - - void setup(void) { - } - - void teardown() { - mock().checkExpectations(); - mock().clear(); - } +class PropertiesTestSuite : public ::testing::Test { +public: }; -TEST(properties, create) { - properties = celix_properties_create(); - CHECK(properties); + +TEST_F(PropertiesTestSuite, create) { + auto* properties = celix_properties_create(); + EXPECT_TRUE(properties); celix_properties_destroy(properties); } -TEST(properties, load) { +TEST_F(PropertiesTestSuite, load) { char propertiesFile[] = "resources-test/properties.txt"; - properties = celix_properties_load(propertiesFile); - LONGS_EQUAL(4, hashMap_size(properties)); + auto* properties = celix_properties_load(propertiesFile); + EXPECT_EQ(4, celix_properties_size(properties)); const char keyA[] = "a"; - const char *valueA = celix_properties_get(properties, keyA, NULL); - STRCMP_EQUAL("b", valueA); + const char *valueA = celix_properties_get(properties, keyA, nullptr); + EXPECT_STREQ("b", valueA); const char keyNiceA[] = "nice_a"; - const char *valueNiceA = celix_properties_get(properties, keyNiceA, NULL); - STRCMP_EQUAL("nice_b", valueNiceA); + const char *valueNiceA = celix_properties_get(properties, keyNiceA, nullptr); + EXPECT_STREQ("nice_b", valueNiceA); const char keyB[] = "b"; - const char *valueB = celix_properties_get(properties, keyB, NULL); - STRCMP_EQUAL("c \t d", valueB); + const char *valueB = celix_properties_get(properties, keyB, nullptr); + EXPECT_STREQ("c \t d", valueB); celix_properties_destroy(properties); } -TEST(properties, asLong) { +TEST_F(PropertiesTestSuite, asLong) { celix_properties_t *props = celix_properties_create(); celix_properties_set(props, "t1", "42"); celix_properties_set(props, "t2", "-42"); @@ -92,59 +64,59 @@ TEST(properties, asLong) { celix_properties_set(props, "t5", "bla"); long v = celix_properties_getAsLong(props, "t1", -1); - LONGS_EQUAL(42, v); + EXPECT_EQ(42, v); v = celix_properties_getAsLong(props, "t2", -1); - LONGS_EQUAL(-42, v); + EXPECT_EQ(-42, v); v = celix_properties_getAsLong(props, "t3", -1); - LONGS_EQUAL(-1, v); + EXPECT_EQ(-1, v); v = celix_properties_getAsLong(props, "t4", -1); - LONGS_EQUAL(42, v); + EXPECT_EQ(42, v); v = celix_properties_getAsLong(props, "t5", -1); - LONGS_EQUAL(-1, v); + EXPECT_EQ(-1, v); v = celix_properties_getAsLong(props, "non-existing", -1); - LONGS_EQUAL(-1, v); + EXPECT_EQ(-1, v); celix_properties_destroy(props); } -TEST(properties, store) { +TEST_F(PropertiesTestSuite, store) { char propertiesFile[] = "resources-test/properties_out.txt"; - properties = celix_properties_create(); + auto* properties = celix_properties_create(); char keyA[] = "x"; char keyB[] = "y"; char valueA[] = "1"; char valueB[] = "2"; celix_properties_set(properties, keyA, valueA); celix_properties_set(properties, keyB, valueB); - celix_properties_store(properties, propertiesFile, NULL); + celix_properties_store(properties, propertiesFile, nullptr); celix_properties_destroy(properties); } -TEST(properties, copy) { +TEST_F(PropertiesTestSuite, copy) { char propertiesFile[] = "resources-test/properties.txt"; - properties = celix_properties_load(propertiesFile); - LONGS_EQUAL(4, hashMap_size(properties)); + auto* properties = celix_properties_load(propertiesFile); + EXPECT_EQ(4, celix_properties_size(properties)); celix_properties_t *copy = celix_properties_copy(properties); char keyA[] = "a"; - const char *valueA = celix_properties_get(copy, keyA, NULL); - STRCMP_EQUAL("b", valueA); + const char *valueA = celix_properties_get(copy, keyA, nullptr); + EXPECT_STREQ("b", valueA); const char keyB[] = "b"; - STRCMP_EQUAL("c \t d", celix_properties_get(copy, keyB, NULL)); + EXPECT_STREQ("c \t d", celix_properties_get(copy, keyB, nullptr)); celix_properties_destroy(properties); celix_properties_destroy(copy); } -TEST(properties, getSet) { - properties = celix_properties_create(); +TEST_F(PropertiesTestSuite, getSet) { + auto* properties = celix_properties_create(); char keyA[] = "x"; char keyB[] = "y"; char keyC[] = "z"; @@ -157,34 +129,34 @@ TEST(properties, getSet) { celix_properties_set(properties, keyB, valueB); celix_properties_setWithoutCopy(properties, keyD, valueD); - STRCMP_EQUAL(valueA, celix_properties_get(properties, keyA, NULL)); - STRCMP_EQUAL(valueB, celix_properties_get(properties, keyB, NULL)); - STRCMP_EQUAL(valueC, celix_properties_get(properties, keyC, valueC)); - STRCMP_EQUAL(valueD, celix_properties_get(properties, keyD, NULL)); + EXPECT_STREQ(valueA, celix_properties_get(properties, keyA, nullptr)); + EXPECT_STREQ(valueB, celix_properties_get(properties, keyB, nullptr)); + EXPECT_STREQ(valueC, celix_properties_get(properties, keyC, valueC)); + EXPECT_STREQ(valueD, celix_properties_get(properties, keyD, nullptr)); celix_properties_destroy(properties); } -TEST(properties, setUnset) { - properties = celix_properties_create(); +TEST_F(PropertiesTestSuite, setUnset) { + auto* properties = celix_properties_create(); char keyA[] = "x"; char *keyD = strndup("a", 1); char valueA[] = "1"; char *valueD = strndup("4", 1); celix_properties_set(properties, keyA, valueA); celix_properties_setWithoutCopy(properties, keyD, valueD); - STRCMP_EQUAL(valueA, celix_properties_get(properties, keyA, NULL)); - STRCMP_EQUAL(valueD, celix_properties_get(properties, keyD, NULL)); + EXPECT_STREQ(valueA, celix_properties_get(properties, keyA, nullptr)); + EXPECT_STREQ(valueD, celix_properties_get(properties, keyD, nullptr)); celix_properties_unset(properties, keyA); celix_properties_unset(properties, keyD); - POINTERS_EQUAL(NULL, celix_properties_get(properties, keyA, NULL)); - POINTERS_EQUAL(NULL, celix_properties_get(properties, "a", NULL)); + EXPECT_EQ(nullptr, celix_properties_get(properties, keyA, nullptr)); + EXPECT_EQ(nullptr, celix_properties_get(properties, "a", nullptr)); celix_properties_destroy(properties); } -TEST(properties, longTest) { - properties = celix_properties_create(); +TEST_F(PropertiesTestSuite, longTest) { + auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "2"); celix_properties_set(properties, "b", "-10032L"); @@ -197,24 +169,24 @@ TEST(properties, longTest) { long d = celix_properties_getAsLong(properties, "d", -1L); long e = celix_properties_getAsLong(properties, "e", -1L); - CHECK_EQUAL(2, a); - CHECK_EQUAL(-10032L, b); - CHECK_EQUAL(-1L, c); - CHECK_EQUAL(-1L, d); - CHECK_EQUAL(-1L, e); + EXPECT_EQ(2, a); + EXPECT_EQ(-10032L, b); + EXPECT_EQ(-1L, c); + EXPECT_EQ(-1L, d); + EXPECT_EQ(-1L, e); celix_properties_setLong(properties, "a", 3L); celix_properties_setLong(properties, "b", -4L); a = celix_properties_getAsLong(properties, "a", -1L); b = celix_properties_getAsLong(properties, "b", -1L); - CHECK_EQUAL(3L, a); - CHECK_EQUAL(-4L, b); + EXPECT_EQ(3L, a); + EXPECT_EQ(-4L, b); celix_properties_destroy(properties); } -TEST(properties, boolTest) { - properties = celix_properties_create(); +TEST_F(PropertiesTestSuite, boolTest) { + auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "true"); celix_properties_set(properties, "b", "false"); @@ -228,39 +200,40 @@ TEST(properties, boolTest) { bool e = celix_properties_getAsBool(properties, "e", false); bool f = celix_properties_getAsBool(properties, "f", true); - CHECK_EQUAL(true, a); - CHECK_EQUAL(false, b); - CHECK_EQUAL(true, c); - CHECK_EQUAL(true, d); - CHECK_EQUAL(false, e); - CHECK_EQUAL(true, f); + EXPECT_EQ(true, a); + EXPECT_EQ(false, b); + EXPECT_EQ(true, c); + EXPECT_EQ(true, d); + EXPECT_EQ(false, e); + EXPECT_EQ(true, f); celix_properties_setBool(properties, "a", true); celix_properties_setBool(properties, "b", false); a = celix_properties_getAsBool(properties, "a", false); b = celix_properties_getAsBool(properties, "b", true); - CHECK_EQUAL(true, a); - CHECK_EQUAL(false, b); + EXPECT_EQ(true, a); + EXPECT_EQ(false, b); celix_properties_destroy(properties); } -TEST(properties, sizeAndIteratorTest) { +TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { celix_properties_t *props = celix_properties_create(); - CHECK_EQUAL(0, celix_properties_size(props)); + EXPECT_EQ(0, celix_properties_size(props)); celix_properties_set(props, "a", "1"); celix_properties_set(props, "b", "2"); - CHECK_EQUAL(2, celix_properties_size(props)); + EXPECT_EQ(2, celix_properties_size(props)); celix_properties_set(props, "c", " 3 "); celix_properties_set(props, "d", "4"); - CHECK_EQUAL(4, celix_properties_size(props)); + EXPECT_EQ(4, celix_properties_size(props)); int count = 0; - const char *_key = NULL; - CELIX_PROPERTIES_FOR_EACH(props, _key) { + const char *key; + CELIX_PROPERTIES_FOR_EACH(props, key) { + EXPECT_NE(key, nullptr); count++; } - CHECK_EQUAL(4, count); + EXPECT_EQ(4, count); celix_properties_destroy(props); } diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 4e56860ce..3796b004a 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -54,14 +54,10 @@ namespace celix { } bool operator==(const celix::PropertiesIterator& rhs) const { - bool sameMap = iter._data1 == rhs.iter._data1; //map - bool sameIndex = iter._data5 == rhs.iter._data5; //index - bool oneIsEnd = end || rhs.end; - if (oneIsEnd) { - return sameMap && end && rhs.end; - } else { - return sameMap && sameIndex; + if (end || rhs.end) { + return end && rhs.end; } + return celix_propertiesIterator_equals(&iter, &rhs.iter); } bool operator!=(const celix::PropertiesIterator& rhs) const { @@ -81,15 +77,16 @@ namespace celix { } void moveToEnd() { - end = true; first = {}; second = {}; + end = true; } + //TODO for C++17 try to update first and second to stringview std::string first{}; std::string second{}; private: - celix_properties_iterator_t iter{nullptr, nullptr, nullptr, 0, 0}; + celix_properties_iterator_t iter{._data = {}}; bool end{false}; }; @@ -414,6 +411,10 @@ namespace celix { return convertToMap(); } #endif + + + //TODO save + //TODO load private: explicit Properties(celix_properties_t* props) : cProps{props, [](celix_properties_t*) { /*nop*/ }} {} diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 3ff1e906e..c704ab602 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -290,7 +290,11 @@ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); * printf("Visiting hash map entry with key %li\n", inter.key); * } * @endcode + * + * @param map The (const celix_long_hash_map_t*) map to iterate over. + * @param iterName A iterName which will be of type celix_long_hash_map_iterator_t to hold the iterator. */ +//TODO test if the macro can be used nested #define CELIX_LONG_HASH_MAP_ITERATE(map, iterName) \ for (celix_long_hash_map_iterator_t iterName = celix_longHashMap_begin(map); !celix_longHashMapIterator_isEnd(&(iterName)); celix_longHashMapIterator_next(&(iterName))) diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 762fcff35..edd1b3cd4 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -17,6 +17,17 @@ * under the License. */ +/** + * @file celix_properties.h + * @brief Header file for the Celix Properties API. + * + * The Celix Properties API provides a means for storing and manipulating key-value pairs, called properties, + * which can be used to store configuration data or metadata for a service, component, or framework configuration. + * Functions are provided for creating and destroying property sets, loading and storing properties from/to a file + * or stream, and setting, getting, and unsetting individual properties. There are also functions for converting + * property values to various types (e.g. long, bool, double) and for iterating over the properties in a set. + */ + #include #include @@ -29,68 +40,262 @@ extern "C" { #endif -typedef struct hashMap celix_properties_t; //opaque struct, TODO try to make this a celix_properties struct +/** + * @brief celix_properties_t is a type that represents a set of key-value pairs called properties, + * which can be used to store configuration data or metadata for a services, components or framework configuration. + */ +typedef struct celix_properties celix_properties_t; + +//TODO rethink the iter. Maybe this can be more closely coupled to celix_string_hash_map? +//typedef struct celix_properties_iterator { +// //private data +// void* _data1; +// void* _data2; +// void* _data3; +// int _data4; +// int _data5; +//} celix_properties_iterator_t; +/** + * @brief Type representing an iterator for iterating over the properties in a property set. + */ typedef struct celix_properties_iterator { - //private data - void* _data1; - void* _data2; - void* _data3; - int _data4; - int _data5; + //private opaque data + char _data[56]; } celix_properties_iterator_t; -/********************************************************************************************************************** - ********************************************************************************************************************** - * Updated API - ********************************************************************************************************************** - **********************************************************************************************************************/ - +/** + * @brief Creates a new empty property set. + * @return A new empty property set. + */ celix_properties_t* celix_properties_create(void); +/** + * @brief Destroys a property set, freeing all associated resources. + * + * @param properties The property set to destroy. If properties is NULL, this function will do nothing. + */ void celix_properties_destroy(celix_properties_t *properties); +/** + * @brief Loads properties from a file. + * + * @param filename The name of the file to load properties from. + * @return A property set containing the properties from the file. + * @retval NULL If an error occurred (e.g. file not found). + */ celix_properties_t* celix_properties_load(const char *filename); + +/** + * @brief Loads properties from a stream. + * + * @param stream The stream to load properties from. + * @return A property set containing the properties from the stream. + * @retval NULL If an error occurred (e.g. invalid format). + */ celix_properties_t* celix_properties_loadWithStream(FILE *stream); +/** + * @brief Loads properties from a string. + * + * @param input The string to load properties from. + * @return A property set containing the properties from the string. + * @retval NULL If an error occurred (e.g. invalid format). + */ celix_properties_t* celix_properties_loadFromString(const char *input); -void celix_properties_store(celix_properties_t *properties, const char *file, const char *header); +/** + * @brief Stores properties to a file. + * + * @param properties The property set to store. + * @param file The name of the file to store the properties to. + * @param header An optional header to write to the file before the properties. + */ +void celix_properties_store(celix_properties_t *properties, const char *file, const char *header); //TODO add return status +/** + * @brief Gets the value of a property. + * + * @param properties The property set to search. + * @param key The key of the property to get. + * @param defaultValue The value to return if the property is not set. + * @return The value of the property, or the default value if the property is not set. + */ const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue); +/** + * @brief Sets the value of a property. + * + * + * @param properties The property set to modify. + * @param key The key of the property to set. + * @param value The value to set the property to. + */ void celix_properties_set(celix_properties_t *properties, const char *key, const char *value); +/** + * @brief Sets the value of a property without copying the key and value strings. + * + * @param properties The property set to modify. + * @param key The key of the property to set. This string will be used directly, so it must not be freed or modified + * after calling this function. + * @param value The value to set the property to. This string will be used directly, so it must not be freed or + * modified after calling this function. + */ void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value); +/** + * @brief Unsets a property, removing it from the property set. + * @param properties The property set to modify. + * @param key The key of the property to unset. + */ void celix_properties_unset(celix_properties_t *properties, const char *key); +/** + * @brief Makes a copy of a property set. + * + * @param properties The property set to copy. + * @return A copy of the given property set. + */ celix_properties_t* celix_properties_copy(const celix_properties_t *properties); +/** + * @brief Gets the value of a property as a long integer. + * + * @param props The property set to search. + * @param key The key of the property to get. + * @param defaultValue The value to return if the property is not set or cannot be converted to a long. + * @return The value of the property as a long, or the default value if the property is not set or cannot be converted + * to a long. + */ long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue); + +/** + * @brief Sets the value of a property to a long integer. + * + * @param props The property set to modify. + * @param key The key of the property to set. + * @param value The long value to set the property to. + */ void celix_properties_setLong(celix_properties_t *props, const char *key, long value); +/** + * @brief Gets the value of a property as a boolean. + * + * @param props The property set to search. + * @param key The key of the property to get. + * @param defaultValue The value to return if the property is not set or cannot be converted to a boolean. + * @return The value of the property as a boolean, or the default value if the property is not set or cannot be + * converted to a boolean. + */ bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue); -void celix_properties_setBool(celix_properties_t *props, const char *key, bool val); +/** + * @brief Sets the value of a property to a boolean. + * + * @param props The property set to modify. + * @param key The key of the property to set. + * @param val The boolean value to set the property to. + */ +void celix_properties_setBool(celix_properties_t *props, const char *key, bool val); +/** + * @brief Sets the value of a property to a double. + * + * @param props The property set to modify. + * @param key The key of the property to set. + * @param val The double value to set the property to. + */ void celix_properties_setDouble(celix_properties_t *props, const char *key, double val); + +/** + * @brief Gets the value of a property as a double. + * + * @param props The property set to search. + * @param key The key of the property to get. + * @param defaultValue The value to return if the property is not set or cannot be converted to a double. + * @return The value of the property as a double, or the default value if the property is not set or cannot be converted to a double. + */ double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue); +/** + * @brief Gets the number of properties in a property set. + * + * @param properties The property set to get the size of. + * @return The number of properties in the property set. + */ int celix_properties_size(const celix_properties_t *properties); +/** + * @brief Constructs a new iterator for iterating over the properties in a property set. + * + * @param properties The property set to iterate over. + * @return A new iterator for the given property set. + */ celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties); + +/** + * @brief Determines whether the iterator has a next property. + * + * @param iter The iterator to check. + * @return true if the iterator has a next property, false otherwise. + */ bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); + +/** + * @brief Gets the next key in the iterator. + * @param iter The iterator to get the next key from. + * @return The next key in the iterator. + */ const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter); -celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter); -#define CELIX_PROPERTIES_FOR_EACH(props, key) \ - for(celix_properties_iterator_t iter = celix_propertiesIterator_construct(props); \ - celix_propertiesIterator_hasNext(&iter), (key) = celix_propertiesIterator_nextKey(&iter);) +/** + * @brief Gets the property set being iterated over. + * + * @param iter The iterator to get the property set from. + * @return The property set being iterated over. + */ +celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter); +/** + * @brief Determines whether two iterators are equal. + * + * @param a The first iterator to compare. + * @param b The second iterator to compare. + * @return true if the iterators are equal, false otherwise. + */ +bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); +/** + * @brief Macro for iterating over the properties in a property set. + * + * @param props The property set to iterate over. + * @param key The variable to use for the current key in the loop. + * + * Example usage: + * @code{.c} + * celix_properties_t *props = celix_properties_create(); + * celix_properties_set(props, "key1", "value1"); + * celix_properties_set(props, "key2", "value2"); + * celix_properties_set(props, "key3", "value3"); + * + * const char* key; + * CELIX_PROPERTIES_FOR_EACH(props, key) { + * printf("%s = %s\n", key, celix_properties_get(props, key, "")); + * } + * @endcode + * Output: + * @code{.c} + * key1 = value1 + * key2 = value2 + * key3 = value3 + * @endcode +*/ +#define CELIX_PROPERTIES_FOR_EACH(props, key) \ + for(celix_properties_iterator_t iter_##key = celix_propertiesIterator_construct(props); \ + celix_propertiesIterator_hasNext(&iter_##key), (key) = celix_propertiesIterator_nextKey(&iter_##key);) #ifdef __cplusplus } diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index c402be4c5..97c1735b6 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -291,6 +291,7 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); /** * @brief Marco to loop over all the entries of a string hash map. * + * * Small example of how to use the iterate macro: * @code * celix_string_hash_map_t* map = ... @@ -298,7 +299,11 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); * printf("Visiting hash map entry with key %s\n", inter.key); * } * @endcode + * + * @param map The (const celix_string_hash_map_t*) map to iterate over. + * @param iterName A iterName which will be of type celix_string_hash_map_iterator_t to hold the iterator. */ +//TODO test if the macro can be used nested #define CELIX_STRING_HASH_MAP_ITERATE(map, iterName) \ for (celix_string_hash_map_iterator_t iterName = celix_stringHashMap_begin(map); !celix_stringHashMapIterator_isEnd(&(iterName)); celix_stringHashMapIterator_next(&(iterName))) diff --git a/libs/utils/include_deprecated/properties.h b/libs/utils/include_deprecated/properties.h index 00e9c785a..8bcbc3db8 100644 --- a/libs/utils/include_deprecated/properties.h +++ b/libs/utils/include_deprecated/properties.h @@ -37,8 +37,8 @@ extern "C" { #endif -typedef hash_map_pt properties_pt __attribute__((deprecated("properties is deprecated use celix_properties instead"))); -typedef hash_map_t properties_t __attribute__((deprecated("properties is deprecated use celix_properties instead"))); +typedef struct celix_properties* properties_pt __attribute__((deprecated("properties is deprecated use celix_properties instead"))); +typedef struct celix_properties properties_t __attribute__((deprecated("properties is deprecated use celix_properties instead"))); UTILS_EXPORT celix_properties_t* properties_create(void); @@ -62,10 +62,11 @@ UTILS_EXPORT void properties_unset(celix_properties_t *properties, const char *k UTILS_EXPORT celix_status_t properties_copy(celix_properties_t *properties, celix_properties_t **copy); +/** TODO refactor #define PROPERTIES_FOR_EACH(props, key) \ for(hash_map_iterator_t iter = hashMapIterator_construct(props); \ hashMapIterator_hasNext(&iter), (key) = (const char*)hashMapIterator_nextKey(&iter);) - +*/ #ifdef __cplusplus } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c new file mode 100644 index 000000000..a83211248 --- /dev/null +++ b/libs/utils/src/properties.c @@ -0,0 +1,523 @@ +/* + * 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 "properties.h" +#include "celix_properties.h" + +#include +#include +#include +#include +#include +#include + +#include "celix_build_assert.h" +#include "utils.h" //TODO try to remove +#include "celix_utils.h" +#include "celix_string_hash_map.h" + + +#define PROPERTIES_INITIAL_HASHMAP_CAPACITY 10 +#define SHORT_PROPERTIES_OPTIMIZATION_SIZE 1024 + +struct celix_properties { + celix_string_hash_map_t* map; + + /** + * buffer used to store the first key/value entries so that no additional memory allocation is needed. + */ + char buffer[SHORT_PROPERTIES_OPTIMIZATION_SIZE]; +}; + + +#define MALLOC_BLOCK_SIZE 5 + +static void parseLine(const char* line, celix_properties_t *props); + +properties_pt properties_create(void) { + return celix_properties_create(); +} + +void properties_destroy(properties_pt properties) { + celix_properties_destroy(properties); +} + +properties_pt properties_load(const char* filename) { + return celix_properties_load(filename); +} + +properties_pt properties_loadWithStream(FILE *file) { + return celix_properties_loadWithStream(file); +} + +properties_pt properties_loadFromString(const char *input){ + return celix_properties_loadFromString(input); +} + + +/** + * Header is ignored for now, cannot handle comments yet + */ +void properties_store(properties_pt properties, const char* filename, const char* header) { + return celix_properties_store(properties, filename, header); +} + +celix_status_t properties_copy(properties_pt properties, properties_pt *out) { + celix_properties_t *copy = celix_properties_copy(properties); + *out = copy; + return copy == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS; +} + +const char* properties_get(properties_pt properties, const char* key) { + return celix_properties_get(properties, key, NULL); +} + +const char* properties_getWithDefault(properties_pt properties, const char* key, const char* defaultValue) { + return celix_properties_get(properties, key, defaultValue); +} + +void properties_set(properties_pt properties, const char* key, const char* value) { + celix_properties_set(properties, key, value); +} + +void properties_unset(properties_pt properties, const char* key) { + celix_properties_unset(properties, key); +} + +static void updateBuffers(char **key, char ** value, char **output, int outputPos, int *key_len, int *value_len) { + if (*output == *key) { + if (outputPos == (*key_len) - 1) { + (*key_len) += MALLOC_BLOCK_SIZE; + *key = realloc(*key, *key_len); + *output = *key; + } + } + else { + if (outputPos == (*value_len) - 1) { + (*value_len) += MALLOC_BLOCK_SIZE; + *value = realloc(*value, *value_len); + *output = *value; + } + } +} + +static void parseLine(const char* line, celix_properties_t *props) { + int linePos = 0; + bool precedingCharIsBackslash = false; + bool isComment = false; + int outputPos = 0; + char *output = NULL; + int key_len = MALLOC_BLOCK_SIZE; + int value_len = MALLOC_BLOCK_SIZE; + linePos = 0; + precedingCharIsBackslash = false; + isComment = false; + output = NULL; + outputPos = 0; + + //Ignore empty lines + if (line[0] == '\n' && line[1] == '\0') { + return; + } + + char *key = calloc(1, key_len); + char *value = calloc(1, value_len); + key[0] = '\0'; + value[0] = '\0'; + + while (line[linePos] != '\0') { + if (line[linePos] == ' ' || line[linePos] == '\t') { + if (output == NULL) { + //ignore + linePos += 1; + continue; + } + } + else { + if (output == NULL) { + output = key; + } + } + if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') { + if (precedingCharIsBackslash) { + //escaped special character + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + precedingCharIsBackslash = false; + } + else { + if (line[linePos] == '#' || line[linePos] == '!') { + if (outputPos == 0) { + isComment = true; + break; + } + else { + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + } + else { // = or : + if (output == value) { //already have a seperator + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + else { + output[outputPos++] = '\0'; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + output = value; + outputPos = 0; + } + } + } + } + else if (line[linePos] == '\\') { + if (precedingCharIsBackslash) { //double backslash -> backslash + output[outputPos++] = '\\'; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + precedingCharIsBackslash = true; + } + else { //normal character + precedingCharIsBackslash = false; + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + linePos += 1; + } + if (output != NULL) { + output[outputPos] = '\0'; + } + + if (!isComment) { + //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); + celix_properties_set(props, utils_stringTrim(key), utils_stringTrim(value)); + } + if(key) { + free(key); + } + if(value) { + free(value); + } + +} + + + +/********************************************************************************************************************** + ********************************************************************************************************************** + * Updated API + ********************************************************************************************************************** + **********************************************************************************************************************/ + +static void celix_properties_removeEntryCallback(void* handle, const char* key, celix_hash_map_value_t val) { + //celix_properties_t* props = handle; + //TODO add check of short properties optimization buffer + free((char*)key); + free(val.ptrValue); +} + + +celix_properties_t* celix_properties_create(void) { + celix_properties_t* props = malloc(sizeof(*props)); + if (props != NULL) { + celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; + opts.storeKeysWeakly = true; + opts.initialCapacity = PROPERTIES_INITIAL_HASHMAP_CAPACITY; + opts.removedCallbackData = props; + opts.removedCallback = celix_properties_removeEntryCallback; + props->map = celix_stringHashMap_createWithOptions(&opts); + } + return props; +} + +void celix_properties_destroy(celix_properties_t *props) { + if (props != NULL) { + celix_stringHashMap_destroy(props->map); + free(props); + } +} + +celix_properties_t* celix_properties_load(const char *filename) { + FILE *file = fopen(filename, "r"); + if (file == NULL) { + return NULL; + } + celix_properties_t *props = celix_properties_loadWithStream(file); + fclose(file); + return props; +} + +celix_properties_t* celix_properties_loadWithStream(FILE *file) { + celix_properties_t *props = NULL; + + if (file != NULL ) { + char *saveptr; + char *filebuffer = NULL; + char *line = NULL; + size_t file_size = 0; + + props = celix_properties_create(); + fseek(file, 0, SEEK_END); + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + if (file_size > 0) { + filebuffer = calloc(file_size + 1, sizeof(char)); + if (filebuffer) { + size_t rs = fread(filebuffer, sizeof(char), file_size, file); + if (rs != file_size) { + fprintf(stderr,"fread read only %lu bytes out of %lu\n", (long unsigned int) rs, (long unsigned int) file_size); + } + filebuffer[file_size]='\0'; + line = strtok_r(filebuffer, "\n", &saveptr); + while (line != NULL) { + parseLine(line, props); + line = strtok_r(NULL, "\n", &saveptr); + } + free(filebuffer); + } + } + } + + return props; +} + +celix_properties_t* celix_properties_loadFromString(const char *input) { + celix_properties_t *props = celix_properties_create(); + + char *in = strdup(input); + char *line = NULL; + char *saveLinePointer = NULL; + + bool firstTime = true; + do { + if (firstTime){ + line = strtok_r(in, "\n", &saveLinePointer); + firstTime = false; + }else { + line = strtok_r(NULL, "\n", &saveLinePointer); + } + + if (line == NULL){ + break; + } + + parseLine(line, props); + } while(line != NULL); + + free(in); + + return props; +} + +/** + * @brief Store properties string to file and escape the characters '#', '!', '=' and ':' if encountered. + */ +static void celix_properties_storeEscapedString(FILE* file, const char* str) { + for (int i = 0; i < strlen(str); i += 1) { + if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') { + fputc('\\', file); + } + fputc(str[i], file); + } +} + +void celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { + FILE *file = fopen (filename, "w+" ); + + if (file == NULL) { + perror("File is null"); + return; + } + + CELIX_STRING_HASH_MAP_ITERATE(properties->map, iter) { + const char* val = iter.value.ptrValue; + celix_properties_storeEscapedString(file, iter.key); + fputc('=', file); + celix_properties_storeEscapedString(file, val); + fputc('\n', file); + + } + fclose(file); +} + +celix_properties_t* celix_properties_copy(const celix_properties_t *properties) { + celix_properties_t *copy = celix_properties_create(); + if (properties == NULL) { + return copy; + } + + CELIX_STRING_HASH_MAP_ITERATE(properties->map, iter) { + celix_properties_set(copy, iter.key, iter.value.ptrValue); + } + return copy; +} + +const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { + const char* value = NULL; + if (properties != NULL) { + value = celix_stringHashMap_get(properties->map, key); + } + return value == NULL ? defaultValue : value; +} + +void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { + if (properties != NULL) { + celix_stringHashMap_put(properties->map, celix_utils_strdup(key), celix_utils_strdup(value)); + } +} + +void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { + if (properties != NULL) { + if (properties != NULL) { + celix_stringHashMap_put(properties->map, key, value); + } + } +} + +void celix_properties_unset(celix_properties_t *properties, const char *key) { + if (properties != NULL) { + celix_stringHashMap_remove(properties->map, key); + } +} + +long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue) { + long result = defaultValue; + const char *val = celix_properties_get(props, key, NULL); + if (val != NULL) { + char *enptr = NULL; + errno = 0; + long r = strtol(val, &enptr, 10); + if (enptr != val && errno == 0) { + result = r; + } + } + return result; +} + +void celix_properties_setLong(celix_properties_t *props, const char *key, long value) { + char buf[32]; //should be enough to store long long int + int writen = snprintf(buf, 32, "%li", value); + if (writen <= 31) { + celix_properties_set(props, key, buf); + } else { + fprintf(stderr,"buf to small for value '%li'\n", value); + } +} + +double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { + double result = defaultValue; + const char *val = celix_properties_get(props, key, NULL); + if (val != NULL) { + char *enptr = NULL; + errno = 0; + double r = strtod(val, &enptr); + if (enptr != val && errno == 0) { + result = r; + } + } + return result; +} + +void celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { + char buf[32]; //should be enough to store long long int + int writen = snprintf(buf, 32, "%f", val); + if (writen <= 31) { + celix_properties_set(props, key, buf); + } else { + fprintf(stderr,"buf to small for value '%f'\n", val); + } +} + +bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { + bool result = defaultValue; + const char *val = celix_properties_get(props, key, NULL); + if (val != NULL) { + char buf[32]; + snprintf(buf, 32, "%s", val); + char *trimmed = utils_stringTrim(buf); + if (strncasecmp("true", trimmed, strlen("true")) == 0) { + result = true; + } else if (strncasecmp("false", trimmed, strlen("false")) == 0) { + result = false; + } + } + return result; +} + +void celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { + celix_properties_set(props, key, val ? "true" : "false"); +} + +int celix_properties_size(const celix_properties_t *properties) { + return (int)celix_stringHashMap_size(properties->map); +} + +typedef struct { + celix_string_hash_map_iterator_t mapIter; + const celix_properties_t* props; +} celix_properties_iterator_internal_t; + +celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties) { + CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); + celix_properties_iterator_internal_t internalIter; + internalIter.mapIter = celix_stringHashMap_begin(properties->map); + internalIter.props = properties; + + celix_properties_iterator_t iter; + memset(&iter._data, 0, sizeof(iter._data)); + memcpy(iter._data, &internalIter, sizeof(internalIter)); + return iter; +} + +bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) { + celix_properties_iterator_internal_t internalIter; + memcpy(&internalIter, iter, sizeof(internalIter)); + return !celix_stringHashMapIterator_isEnd(&internalIter.mapIter); +} + +const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) { + celix_properties_iterator_internal_t internalIter; + memcpy(&internalIter, iter, sizeof(internalIter)); + + //note assigning key first and then move the next, because celix string hash map iter start at the beginning + const char* key = internalIter.mapIter.key; + celix_stringHashMapIterator_next(&internalIter.mapIter); + + memcpy(iter, &internalIter, sizeof(internalIter)); + return key; +} + +bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b) { + celix_properties_iterator_internal_t internalIterA; + memcpy(&internalIterA, a, sizeof(internalIterA)); + celix_properties_iterator_internal_t internalIterB; + memcpy(&internalIterB, b, sizeof(internalIterB)); + return internalIterA.props == internalIterB.props && + internalIterA.mapIter.key == internalIterB.mapIter.key; +} + + +//TODO can the return be const? +//TODO and can this be removed -> look into C++ PropetiesIter +celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter) { + celix_properties_iterator_internal_t internalIter; + memcpy(&internalIter, iter, sizeof(internalIter)); + return (celix_properties_t*)internalIter.props; +} diff --git a/libs/utils/src/utils.c b/libs/utils/src/utils.c index 4ebb938c5..7bbe2f4c6 100644 --- a/libs/utils/src/utils.c +++ b/libs/utils/src/utils.c @@ -60,7 +60,7 @@ bool celix_utils_stringEquals(const char* a, const char* b) { } else if (a == NULL || b == NULL) { return false; } else { - return strncmp(a, b, 1024*124*10) == 0; + return strncmp(a, b, CELIX_UTILS_MAX_STRLEN) == 0; } } From 6156c7028b951d4e191653d75b015a3a6f373997 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 3 Jan 2023 00:00:25 +0100 Subject: [PATCH 03/63] Add initial primitive type support for celix properties entries --- libs/utils/CMakeLists.txt | 6 - libs/utils/gtest/CMakeLists.txt | 1 + libs/utils/gtest/src/PropertiesTestSuite.cc | 142 +++++++ libs/utils/gtest/src/VersionTestSuite.cc | 395 ++++++++++++++++++ libs/utils/include/celix/Properties.h | 2 +- libs/utils/include/celix_properties.h | 295 +++++++++++--- libs/utils/include/celix_version.h | 71 +++- libs/utils/private/test/version_test.cpp | 414 ------------------- libs/utils/src/properties.c | 431 ++++++++++++++++---- libs/utils/src/version.c | 9 + 10 files changed, 1185 insertions(+), 581 deletions(-) create mode 100644 libs/utils/gtest/src/VersionTestSuite.cc delete mode 100644 libs/utils/private/test/version_test.cpp diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 6de893b6d..475b7da35 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -126,10 +126,6 @@ if (ENABLE_TESTING) target_include_directories(filter_test PRIVATE include_deprecated) target_link_libraries(filter_test CppUTest::CppUTest Celix::utils pthread) - add_executable(version_test private/test/version_test.cpp) - target_include_directories(version_test PRIVATE include_deprecated) - target_link_libraries(version_test CppUTest::CppUTest Celix::utils pthread) - add_test(NAME run_array_list_test COMMAND array_list_test) add_test(NAME run_hash_map_test COMMAND hash_map_test) add_test(NAME run_celix_threads_test COMMAND celix_threads_test) @@ -137,7 +133,6 @@ if (ENABLE_TESTING) add_test(NAME run_utils_test COMMAND utils_test) add_test(NAME run_ip_utils_test COMMAND ip_utils_test) add_test(NAME filter_test COMMAND filter_test) - add_test(NAME version_test COMMAND version_test) setup_target_for_coverage(array_list_test) setup_target_for_coverage(hash_map_test) @@ -146,7 +141,6 @@ if (ENABLE_TESTING) setup_target_for_coverage(utils_test) setup_target_for_coverage(ip_utils_test) setup_target_for_coverage(filter_test) - setup_target_for_coverage(version_test) else () message(WARNING "Cannot find CppUTest, deprecated cpputest-based unit test will not be added") endif () #end CppUTest_FOUND diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index dee276f77..1e512fe61 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable(test_utils src/ArrayListTestSuite.cc src/FileUtilsTestSuite.cc src/PropertiesTestSuite.cc + src/VersionTestSuite.cc ${CELIX_UTIL_TEST_SOURCES_FOR_CXX_HEADERS} ) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 62bfa8dd1..b29802823 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -137,6 +137,22 @@ TEST_F(PropertiesTestSuite, getSet) { celix_properties_destroy(properties); } +TEST_F(PropertiesTestSuite, getSetWithNULL) { + auto* properties = celix_properties_create(); + + celix_properties_set(properties, nullptr, "value"); + EXPECT_EQ(celix_properties_size(properties), 0); //NULL key will be ignored + + celix_properties_set(properties, nullptr, nullptr); + EXPECT_EQ(celix_properties_size(properties), 0); //NULL key will be ignored + + celix_properties_set(properties, "key", nullptr); + EXPECT_EQ(celix_properties_size(properties), 0); //NULL value will be ignored + + celix_properties_destroy(properties); +} + + TEST_F(PropertiesTestSuite, setUnset) { auto* properties = celix_properties_create(); char keyA[] = "x"; @@ -237,3 +253,129 @@ TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { celix_properties_destroy(props); } + +TEST_F(PropertiesTestSuite, getType) { + auto* props = celix_properties_create(); + celix_properties_set(props, "string", "value"); + celix_properties_setLong(props, "long", 123); + celix_properties_setDouble(props, "double", 3.14); + celix_properties_setBool(props, "bool", true); + auto* version = celix_version_createVersion(1, 2, 3, nullptr); + celix_properties_setVersion(props, "version", version); + + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, celix_properties_getType(props, "string")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, celix_properties_getType(props, "long")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, celix_properties_getType(props, "double")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_BOOL, celix_properties_getType(props, "bool")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "version")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, celix_properties_getType(props, "missing")); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, getEntry) { + auto *props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_setLong(props, "key2", 123); + celix_properties_setDouble(props, "key3", 123.456); + celix_properties_setBool(props, "key4", true); + auto *version = celix_version_createVersion(1, 2, 3, nullptr); + celix_properties_setVersion(props, "key5", version); + + auto entry = celix_properties_getEntry(props, "key1"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, entry.valueType); + EXPECT_STREQ("value1", entry.value); + EXPECT_STREQ("value1", entry.typed.strValue); + + entry = celix_properties_getEntry(props, "key2"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, entry.valueType); + EXPECT_STREQ("123", entry.value); + EXPECT_EQ(123, entry.typed.longValue); + + entry = celix_properties_getEntry(props, "key3"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, entry.valueType); + EXPECT_NE(strstr(entry.value, "123.456"), nullptr); + EXPECT_DOUBLE_EQ(123.456, entry.typed.doubleValue); + + entry = celix_properties_getEntry(props, "key4"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_BOOL, entry.valueType); + EXPECT_STREQ("true", entry.value); + EXPECT_TRUE(entry.typed.boolValue); + + entry = celix_properties_getEntry(props, "key5"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, entry.valueType); + EXPECT_STREQ("1.2.3", entry.value); + EXPECT_EQ(1, celix_version_getMajor(entry.typed.versionValue)); + EXPECT_EQ(2, celix_version_getMinor(entry.typed.versionValue)); + EXPECT_EQ(3, celix_version_getMicro(entry.typed.versionValue)); + + entry = celix_properties_getEntry(props, "key6"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, entry.valueType); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, iteratorNextKey) { + auto *props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_set(props, "key2", "value2"); + celix_properties_set(props, "key3", "value3"); + auto iter = celix_propertiesIterator_construct(props); + const char* key; + int count = 0; + while (celix_propertiesIterator_hasNext(&iter)) { + key = celix_propertiesIterator_nextKey(&iter); + EXPECT_NE(strstr(key, "key"), nullptr); + count++; + } + EXPECT_EQ(count, 3); + key = celix_propertiesIterator_nextKey(&iter); + EXPECT_EQ(nullptr, key) << "got key: " << key; + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, iteratorNext) { + auto *props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_set(props, "key2", "value2"); + celix_properties_set(props, "key3", "value3"); + + int count = 0; + auto iter = celix_properties_begin(props); + while (!celix_propertiesIterator_isEnd(&iter)) { + EXPECT_NE(strstr(iter.entry.key, "key"), nullptr); + EXPECT_NE(strstr(iter.entry.value, "value"), nullptr); + count++; + celix_propertiesIterator_next(&iter); + } + EXPECT_EQ(count, 3); + celix_propertiesIterator_next(&iter); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, iter.entry.valueType); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, iterateOverProperties) { + celix_properties_t* props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_set(props, "key2", "value2"); + + int outerCount = 0; + int innerCount = 0; + CELIX_PROPERTIES_ITERATE(props, outerIter) { + outerCount++; + EXPECT_NE(strstr(outerIter.entry.key, "key"), nullptr); + + // Inner loop to test nested iteration + CELIX_PROPERTIES_ITERATE(props, innerIter) { + innerCount++; + EXPECT_NE(strstr(innerIter.entry.key, "key"), nullptr); + } + } + // Check that both entries were iterated over + EXPECT_EQ(outerCount, 2); + EXPECT_EQ(innerCount, 4); + + celix_properties_destroy(props); +} \ No newline at end of file diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc new file mode 100644 index 000000000..a0874ecf7 --- /dev/null +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -0,0 +1,395 @@ +/* + * 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 + +#include "celix_errno.h" +#include "celix_version.h" +#include "celix_utils.h" +#include "version.h" + +extern "C" +{ +#include "version_private.h" +} + +class VersionTestSuite : public ::testing::Test {}; + +TEST_F(VersionTestSuite, create) { + celix_version_t* version = nullptr; + char * str; + + str = celix_utils_strdup("abc"); + EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + EXPECT_STREQ("abc", version->qualifier); + + version_destroy(version); + version = nullptr; + EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, nullptr, &version)); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + EXPECT_STREQ("", version->qualifier); + + version_destroy(version); + version = nullptr; + free(str); + str = celix_utils_strdup("abc"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(-1, -2, -3, str, &version)); + + version_destroy(version); + version = nullptr; + free(str); + str = celix_utils_strdup("abc|xyz"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(1, 2, 3, str, &version)); + + version_destroy(version); + free(str); +} + +TEST_F(VersionTestSuite, clone) { + celix_version_t* version = nullptr; + celix_version_t* clone = nullptr; + char * str; + + str = celix_utils_strdup("abc"); + EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); + EXPECT_EQ(CELIX_SUCCESS, version_clone(version, &clone)); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, clone->major); + EXPECT_EQ(2, clone->minor); + EXPECT_EQ(3, clone->micro); + EXPECT_STREQ("abc", clone->qualifier); + + version_destroy(clone); + version_destroy(version); + free(str); +} + +TEST_F(VersionTestSuite, createFromString) { + celix_version_t* version = nullptr; + celix_status_t status = CELIX_SUCCESS; + char * str; + + str = celix_utils_strdup("1"); + EXPECT_EQ(CELIX_SUCCESS, version_createVersionFromString(str, &version)); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + + version_destroy(version); + + free(str); + str = celix_utils_strdup("a"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); + + free(str); + str = celix_utils_strdup("1.a"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); + + free(str); + str = celix_utils_strdup("1.1.a"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); + + free(str); + str = celix_utils_strdup("-1"); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); + + free(str); + str = celix_utils_strdup("1.2"); + version = nullptr; + EXPECT_EQ(CELIX_SUCCESS, version_createVersionFromString(str, &version)); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + + version_destroy(version); + + free(str); + str = celix_utils_strdup("1.2.3"); + version = nullptr; + status = version_createVersionFromString(str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + + version_destroy(version); + free(str); + str = celix_utils_strdup("1.2.3.abc"); + version = nullptr; + status = version_createVersionFromString(str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + EXPECT_STREQ("abc", version->qualifier); + + version_destroy(version); + free(str); + str = celix_utils_strdup("1.2.3.abc_xyz"); + version = nullptr; + status = version_createVersionFromString(str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + EXPECT_STREQ("abc_xyz", version->qualifier); + + version_destroy(version); + free(str); + str = celix_utils_strdup("1.2.3.abc-xyz"); + version = nullptr; + status = version_createVersionFromString(str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(1, version->major); + EXPECT_EQ(2, version->minor); + EXPECT_EQ(3, version->micro); + EXPECT_STREQ("abc-xyz", version->qualifier); + + version_destroy(version); + free(str); + str = celix_utils_strdup("1.2.3.abc|xyz"); + status = version_createVersionFromString(str, &version); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); + + free(str); +} + +TEST_F(VersionTestSuite, createEmptyVersion) { + celix_version_t* version = nullptr; + celix_status_t status = CELIX_SUCCESS; + + status = version_createEmptyVersion(&version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + EXPECT_EQ(0, version->major); + EXPECT_EQ(0, version->minor); + EXPECT_EQ(0, version->micro); + EXPECT_STREQ("", version->qualifier); + + version_destroy(version); +} + +TEST_F(VersionTestSuite, getters) { + celix_version_t* version = nullptr; + celix_status_t status = CELIX_SUCCESS; + char * str; + int major, minor, micro; + const char *qualifier; + + str = celix_utils_strdup("abc"); + status = version_createVersion(1, 2, 3, str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + + version_getMajor(version, &major); + EXPECT_EQ(1, major); + + version_getMinor(version, &minor); + EXPECT_EQ(2, minor); + + version_getMicro(version, µ); + EXPECT_EQ(3, micro); + + version_getQualifier(version, &qualifier); + EXPECT_STREQ("abc", qualifier); + + version_destroy(version); + free(str); +} + +TEST_F(VersionTestSuite, compare) { + celix_version_t* version = nullptr; + celix_version_t* compare = nullptr; + celix_status_t status = CELIX_SUCCESS; + char * str; + int result; + + // Base version to compare + str = celix_utils_strdup("abc"); + status = version_createVersion(1, 2, 3, str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + + // Compare equality + free(str); + str = celix_utils_strdup("abc"); + compare = nullptr; + status = version_createVersion(1, 2, 3, str, &compare); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + status = version_compareTo(version, compare, &result); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_EQ(0, result); + + // Compare against a higher version + free(str); + str = celix_utils_strdup("bcd"); + version_destroy(compare); + compare = nullptr; + status = version_createVersion(1, 2, 3, str, &compare); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + status = version_compareTo(version, compare, &result); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(result < 0); + + // Compare againts a lower version + free(str); + str = celix_utils_strdup("abc"); + version_destroy(compare); + compare = nullptr; + status = version_createVersion(1, 1, 3, str, &compare); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + status = version_compareTo(version, compare, &result); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(result > 0); + + version_destroy(compare); + version_destroy(version); + free(str); +} + +TEST_F(VersionTestSuite, celix_version_compareToMajorMinor) { + celix_version_t *version1 = celix_version_createVersion(2, 2, 0, nullptr); + celix_version_t *version2 = celix_version_createVersion(2, 2, 4, "qualifier"); + + EXPECT_EQ(0, celix_version_compareToMajorMinor(version1, 2, 2)); + EXPECT_EQ(0, celix_version_compareToMajorMinor(version2, 2, 2)); + + EXPECT_TRUE(celix_version_compareToMajorMinor(version1, 2, 3) < 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version2, 2, 3) < 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version1, 3, 3) < 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version2, 3, 3) < 0); + + + EXPECT_TRUE(celix_version_compareToMajorMinor(version1, 2, 1) > 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version2, 2, 1) > 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version1, 1, 1) > 0); + EXPECT_TRUE(celix_version_compareToMajorMinor(version2, 1, 1) > 0); + + celix_version_destroy(version1); + celix_version_destroy(version2); +} + +TEST_F(VersionTestSuite, toString) { + celix_version_t* version = nullptr; + celix_status_t status = CELIX_SUCCESS; + char * str; + char *result = nullptr; + + str = celix_utils_strdup("abc"); + status = version_createVersion(1, 2, 3, str, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + + status = version_toString(version, &result); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(result != nullptr); + EXPECT_STREQ("1.2.3.abc", result); + free(result); + + version_destroy(version); + version = nullptr; + status = version_createVersion(1, 2, 3, nullptr, &version); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(version != nullptr); + + status = version_toString(version, &result); + EXPECT_EQ(CELIX_SUCCESS, status); + EXPECT_TRUE(result != nullptr); + EXPECT_STREQ("1.2.3", result); + + version_destroy(version); + free(result); + free(str); +} + +TEST_F(VersionTestSuite,semanticCompatibility) { + celix_version_t* provider = nullptr; + celix_version_t* compatible_user = nullptr; + celix_version_t* incompatible_user_by_major = nullptr; + celix_version_t* incompatible_user_by_minor = nullptr; + celix_status_t status = CELIX_SUCCESS; + bool isCompatible = false; + + status = version_isCompatible(compatible_user, provider, &isCompatible); + EXPECT_EQ(CELIX_SUCCESS, status); + + version_createVersion(2, 3, 5, nullptr, &provider); + version_createVersion(2, 1, 9, nullptr, &compatible_user); + version_createVersion(1, 3, 5, nullptr, &incompatible_user_by_major); + version_createVersion(2, 5, 7, nullptr, &incompatible_user_by_minor); + + status = version_isCompatible(compatible_user, provider, &isCompatible); + EXPECT_TRUE(isCompatible == true); + EXPECT_EQ(CELIX_SUCCESS, status); + + status = version_isCompatible(incompatible_user_by_major, provider, &isCompatible); + EXPECT_TRUE(isCompatible == false); + EXPECT_EQ(CELIX_SUCCESS, status); + + status = version_isCompatible(incompatible_user_by_minor, provider, &isCompatible); + EXPECT_TRUE(isCompatible == false); + EXPECT_EQ(CELIX_SUCCESS, status); + + version_destroy(provider); + version_destroy(compatible_user); + version_destroy(incompatible_user_by_major); + version_destroy(incompatible_user_by_minor); +} + +TEST_F(VersionTestSuite, compareEmptyAndNullQualifier) { + //nullptr or "" qualifier should be the same + auto* v1 = celix_version_createVersion(0, 0, 0, nullptr); + auto* v2 = celix_version_createVersion(0, 0, 0, ""); + EXPECT_EQ(0, celix_version_compareTo(v1, v1)); + EXPECT_EQ(0, celix_version_compareTo(v1, v2)); + EXPECT_EQ(0, celix_version_compareTo(v2, v2)); + + celix_version_destroy(v1); + celix_version_destroy(v2); +} + +TEST_F(VersionTestSuite, fillString) { + // Create a version object + celix_version_t* version = celix_version_createVersion(1, 2, 3, "alpha"); + + // Test with buffer large enough to hold the formatted string + char buffer[32]; + bool success = celix_version_fillString(version, buffer, 32); + EXPECT_TRUE(success); + EXPECT_STREQ("1.2.3.alpha", buffer); + + // Test with buffer too small to hold the formatted string + success = celix_version_fillString(version, buffer, 5); + EXPECT_FALSE(success); + + celix_version_destroy(version); +} \ No newline at end of file diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 3796b004a..da6536168 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -86,7 +86,7 @@ namespace celix { std::string first{}; std::string second{}; private: - celix_properties_iterator_t iter{._data = {}}; + celix_properties_iterator_t iter{.index = -1, .entry = {}, ._data = {}}; bool end{false}; }; diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index edd1b3cd4..3c8766978 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -22,16 +22,16 @@ * @brief Header file for the Celix Properties API. * * The Celix Properties API provides a means for storing and manipulating key-value pairs, called properties, - * which can be used to store configuration data or metadata for a service, component, or framework configuration. + * which can be used to store configuration data or metadata for a services, components, or framework configuration. * Functions are provided for creating and destroying property sets, loading and storing properties from/to a file * or stream, and setting, getting, and unsetting individual properties. There are also functions for converting * property values to various types (e.g. long, bool, double) and for iterating over the properties in a set. */ #include -#include #include "celix_errno.h" +#include "celix_version.h" #ifndef CELIX_PROPERTIES_H_ #define CELIX_PROPERTIES_H_ @@ -46,25 +46,59 @@ extern "C" { */ typedef struct celix_properties celix_properties_t; -//TODO rethink the iter. Maybe this can be more closely coupled to celix_string_hash_map? -//typedef struct celix_properties_iterator { -// //private data -// void* _data1; -// void* _data2; -// void* _data3; -// int _data4; -// int _data5; -//} celix_properties_iterator_t; +/** + * @brief Enum representing the possible types of a property value. + */ +typedef enum celix_properties_value_type { + CELIX_PROPERTIES_VALUE_TYPE_UNSET = 0, /**< Property value is not set. */ + CELIX_PROPERTIES_VALUE_TYPE_STRING = 1, /**< Property value is a string. */ + CELIX_PROPERTIES_VALUE_TYPE_LONG = 2, /**< Property value is a long integer. */ + CELIX_PROPERTIES_VALUE_TYPE_DOUBLE = 3, /**< Property value is a double. */ + CELIX_PROPERTIES_VALUE_TYPE_BOOL = 4, /**< Property value is a boolean. */ + CELIX_PROPERTIES_VALUE_TYPE_VERSION = 5 /**< Property value is a Celix version. */ +} celix_properties_value_type_e; + +/** + * @brief A structure representing a single entry in a property set. + */ +typedef struct celix_properties_entry { + const char* key; /**< The key of the entry*/ + const char* value; /**< The string value or string representation of a non-string + typed value.*/ + celix_properties_value_type_e valueType; /**< The type of the value of the entry */ + + union { + const char* strValue; /**< The string value of the entry. */ + long longValue; /**< The long integer value of the entry. */ + double doubleValue; /**< The double-precision floating point value of the entry. */ + bool boolValue; /**< The boolean value of the entry. */ + celix_version_t* versionValue; /**< The Celix version value of the entry. */ + } typed; /**< The typed values of the entry. Only valid if valueType + is not CELIX_PROPERTIES_VALUE_TYPE_UNSET and only the matching + value types should be used. E.g typed.boolValue if valueType is + CELIX_PROPERTIES_VALUE_TYPE_BOOL. */ +} celix_properties_entry_t; /** - * @brief Type representing an iterator for iterating over the properties in a property set. + * @brief Represents an iterator for iterating over the entries in a celix_properties_t object. */ typedef struct celix_properties_iterator { - //private opaque data + /** + * @brief The index of the current iterator. + */ + int index; + + /** + * @brief The current entry. + */ + celix_properties_entry_t entry; + + /** + * @brief Private data used to implement the iterator. + */ char _data[56]; } celix_properties_iterator_t; - /** * @brief Creates a new empty property set. * @return A new empty property set. @@ -76,7 +110,7 @@ celix_properties_t* celix_properties_create(void); * * @param properties The property set to destroy. If properties is NULL, this function will do nothing. */ -void celix_properties_destroy(celix_properties_t *properties); +void celix_properties_destroy(celix_properties_t* properties); /** * @brief Loads properties from a file. @@ -112,8 +146,20 @@ celix_properties_t* celix_properties_loadFromString(const char *input); * @param properties The property set to store. * @param file The name of the file to store the properties to. * @param header An optional header to write to the file before the properties. + * @return CELIX_SUCCESS if the operation was successful, CELIX_FILE_IO_EXCEPTION if there was an error writing to the + * file. + */ +celix_status_t celix_properties_store(celix_properties_t* properties, const char* file, const char* header); + +/** + * @brief Gets the entry for a given key in a property set. + * + * @param properties The property set to search. + * @param key The key to search for. + * @return The entry for the given key, or a default entry with the valueType set to CELIX_PROPERTIES_VALUE_TYPE_UNSET + * if the key is not found. */ -void celix_properties_store(celix_properties_t *properties, const char *file, const char *header); //TODO add return status +celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* properties, const char* key); /** * @brief Gets the value of a property. @@ -123,7 +169,15 @@ void celix_properties_store(celix_properties_t *properties, const char *file, co * @param defaultValue The value to return if the property is not set. * @return The value of the property, or the default value if the property is not set. */ -const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue); +const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); + +/** + * @brief Gets the type of a property value. + * @param properties The property set to search. + * @param key The key of the property to get the type of. + * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set. + */ +celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key); /** * @brief Sets the value of a property. @@ -133,7 +187,7 @@ const char* celix_properties_get(const celix_properties_t *properties, const cha * @param key The key of the property to set. * @param value The value to set the property to. */ -void celix_properties_set(celix_properties_t *properties, const char *key, const char *value); +void celix_properties_set(celix_properties_t* properties, const char* key, const char *value); /** * @brief Sets the value of a property without copying the key and value strings. @@ -144,14 +198,14 @@ void celix_properties_set(celix_properties_t *properties, const char *key, const * @param value The value to set the property to. This string will be used directly, so it must not be freed or * modified after calling this function. */ -void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value); +void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); /** * @brief Unsets a property, removing it from the property set. * @param properties The property set to modify. * @param key The key of the property to unset. */ -void celix_properties_unset(celix_properties_t *properties, const char *key); +void celix_properties_unset(celix_properties_t* properties, const char *key); /** * @brief Makes a copy of a property set. @@ -159,66 +213,100 @@ void celix_properties_unset(celix_properties_t *properties, const char *key); * @param properties The property set to copy. * @return A copy of the given property set. */ -celix_properties_t* celix_properties_copy(const celix_properties_t *properties); +celix_properties_t* celix_properties_copy(const celix_properties_t* properties); /** * @brief Gets the value of a property as a long integer. * - * @param props The property set to search. + * @param properties The property set to search. * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set or cannot be converted to a long. - * @return The value of the property as a long, or the default value if the property is not set or cannot be converted - * to a long. + * @param defaultValue The value to return if the property is not set, the value is not a long integer, + * or if the value cannot be converted to a long integer. + * @return The value of the property as a long integer, or the default value if the property is not set, + * the value is not a long integer, or if the value cannot be converted to a long integer. + * If the value is a string, it will be converted to a long integer if possible. */ -long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue); +long celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); /** * @brief Sets the value of a property to a long integer. * - * @param props The property set to modify. + * @param properties The property set to modify. * @param key The key of the property to set. * @param value The long value to set the property to. */ -void celix_properties_setLong(celix_properties_t *props, const char *key, long value); +void celix_properties_setLong(celix_properties_t* properties, const char* key, long value); /** * @brief Gets the value of a property as a boolean. * - * @param props The property set to search. + * @param properties The property set to search. * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set or cannot be converted to a boolean. - * @return The value of the property as a boolean, or the default value if the property is not set or cannot be - * converted to a boolean. + * @param defaultValue The value to return if the property is not set, the value is not a boolean, or if the value + * cannot be converted to a boolean. + * @return The value of the property as a boolean, or the default value if the property is not set, the value is not a + * boolean, or if the value cannot be converted to a boolean. If the value is a string, it will be converted + * to a boolean if possible. */ -bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue); +bool celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); /** * @brief Sets the value of a property to a boolean. * - * @param props The property set to modify. + * @param properties The property set to modify. * @param key The key of the property to set. * @param val The boolean value to set the property to. */ -void celix_properties_setBool(celix_properties_t *props, const char *key, bool val); +void celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); /** * @brief Sets the value of a property to a double. * - * @param props The property set to modify. + * @param properties The property set to modify. * @param key The key of the property to set. * @param val The double value to set the property to. */ -void celix_properties_setDouble(celix_properties_t *props, const char *key, double val); +void celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); /** * @brief Gets the value of a property as a double. * - * @param props The property set to search. + * @param properties The property set to search. * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set or cannot be converted to a double. - * @return The value of the property as a double, or the default value if the property is not set or cannot be converted to a double. + * @param defaultValue The value to return if the property is not set, the value is not a double, + * or if the value cannot be converted to a double. + * @return The value of the property as a double, or the default value if the property is not set, the value is not + * a double, or if the value cannot be converted to a double. If the value is a string, it will be converted + * to a double if possible. + */ +double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); + +/** + * @brief Sets the value of a property as a Celix version string. + * + * @param properties The property set to modify. + * @param key The key of the property to set. + * @param version The value to set. */ -double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue); +void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); + +/** + * @brief Gets the value of a property as a Celix version. + * + * //TODO, maybe improve, now returns NULL if underlining type is not version + * + * @param properties The property set to search. + * @param key The key of the property to get. + * @param defaultValue The value to return if the property is not set, the value is not a Celix version, or if + * the value cannot be converted to a Celix version. + * @return The value of the property as a Celix version, or the default value if the property is not set, + * the value is not a Celix version, or if the value cannot be converted to a Celix version. + * If the value is a string, it will be verified as a valid Celix version. + */ +const celix_version_t* celix_properties_getAsVersion( + const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue); /** * @brief Gets the number of properties in a property set. @@ -226,44 +314,81 @@ double celix_properties_getAsDouble(const celix_properties_t *props, const char * @param properties The property set to get the size of. * @return The number of properties in the property set. */ -int celix_properties_size(const celix_properties_t *properties); +int celix_properties_size(const celix_properties_t* properties); /** - * @brief Constructs a new iterator for iterating over the properties in a property set. + * @brief Constructs a new properties iterator. + * + * Note: The iterator is initialized to be before the first entry. To advance to the first entry, + * call `celix_propertiesIterator_nextEntry`. * - * @param properties The property set to iterate over. - * @return A new iterator for the given property set. + * //TODO deprecate? + * + * @param[in] properties The properties object to iterate over. + * @return The newly constructed iterator. */ -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties); +celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties); /** - * @brief Determines whether the iterator has a next property. + * @brief Determines whether the iterator has more entries. + * + * //TODO deprecate? * - * @param iter The iterator to check. - * @return true if the iterator has a next property, false otherwise. + * @param[in] iter The iterator. + * @return true if the iterator has more entries, false otherwise. */ bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); /** - * @brief Gets the next key in the iterator. - * @param iter The iterator to get the next key from. - * @return The next key in the iterator. + * @brief Advances the iterator to the next entry and returns the key for the current entry. + * + * If the iterator has no more entries, this function returns NULL. + * + * //TODO deprecate? + * + * @param[in, out] iter The iterator. + * @return The key for the current entry, or NULL if the iterator has no more entries. + */ +const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter); + +/** + * @brief Constructs an iterator pointing to the first entry in the properties object. + * + * @param[in] properties The properties object to iterate over. + * @return The iterator pointing to the first entry in the properties object. + */ +celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties); + +/** + * @brief Advances the iterator to the next entry. + * + * @param[in, out] iter The iterator. + */ +void celix_propertiesIterator_next(celix_properties_iterator_t* iter); + +/** + * @brief Determines whether the iterator is pointing to an end position. + * + * An iterator is at an end position if it has no more entries to visit. + * + * @param[in] iter The iterator. + * @return true if the iterator is at an end position, false otherwise. */ -const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter); +bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); /** * @brief Gets the property set being iterated over. * - * @param iter The iterator to get the property set from. + * @param[in] iter The iterator to get the property set from. * @return The property set being iterated over. */ -celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter); +celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter); /** * @brief Determines whether two iterators are equal. * - * @param a The first iterator to compare. - * @param b The second iterator to compare. + * @param[in] a The first iterator to compare. + * @param[in] b The second iterator to compare. * @return true if the iterators are equal, false otherwise. */ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); @@ -271,21 +396,24 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const /** * @brief Macro for iterating over the properties in a property set. * - * @param props The property set to iterate over. - * @param key The variable to use for the current key in the loop. + * @param[in] properties The property set to iterate over. + * @param[out] key The variable to use for the current key in the loop. + * + * //TODO deprecate * * Example usage: * @code{.c} - * celix_properties_t *props = celix_properties_create(); - * celix_properties_set(props, "key1", "value1"); - * celix_properties_set(props, "key2", "value2"); - * celix_properties_set(props, "key3", "value3"); + * celix_properties_t* properties = celix_properties_create(); + * celix_properties_set(properties, "key1", "value1"); + * celix_properties_set(properties, "key2", "value2"); + * celix_properties_set(properties, "key3", "value3"); * * const char* key; - * CELIX_PROPERTIES_FOR_EACH(props, key) { - * printf("%s = %s\n", key, celix_properties_get(props, key, "")); + * CELIX_PROPERTIES_FOR_EACH(properties, key) { + * printf("%s = %s\n", key, celix_properties_get(properties, key, "")); * } * @endcode + * * Output: * @code{.c} * key1 = value1 @@ -293,9 +421,40 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const * key3 = value3 * @endcode */ -#define CELIX_PROPERTIES_FOR_EACH(props, key) \ - for(celix_properties_iterator_t iter_##key = celix_propertiesIterator_construct(props); \ - celix_propertiesIterator_hasNext(&iter_##key), (key) = celix_propertiesIterator_nextKey(&iter_##key);) +#define CELIX_PROPERTIES_FOR_EACH(map, keyName) \ + for (celix_properties_iterator_t iter_##keyName = celix_properties_begin(map); \ + (keyName) = iter_##keyName.entry.key, !celix_propertiesIterator_isEnd(&(iter_##keyName)); \ + celix_propertiesIterator_next(&(iter_##keyName))) + +/* +#define CELIX_PROPERTIES_FOR_EACH(properties, key) \ + for(celix_properties_iterator_t iter_##key = celix_propertiesIterator_construct(properties); \ + celix_propertiesIterator_hasNext(&iter_##key), (key) = celix_propertiesIterator_nextKey(&iter_##key);) +*/ + + +/** + * @brief Iterates over the entries in the specified celix_properties_t object. + * + * This macro allows you to easily iterate over the entries in a celix_properties_t object. + * The loop variable `iterName` will be of type celix_properties_iterator_t and will contain the current + * entry during each iteration. + * + * @param map The properties object to iterate over. + * @param iterName The name of the iterator variable to use in the loop. + * + * Example usage: + * @code{.c} + * // Iterate over all entries in the properties object + * CELIX_PROPERTIES_ITERATE(properties, iter) { + * // Print the key and value of the current entry + * printf("%s: %s\n", iter.entry.key, iter.entry.value); + * } + * @endcode + */ +#define CELIX_PROPERTIES_ITERATE(map, iterName) \ + for (celix_properties_iterator_t iterName = celix_properties_begin(map); \ + !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) #ifdef __cplusplus } diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index a5b7073d9..be03aeef1 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -27,18 +27,18 @@ extern "C" { #include /** - * The definition of the celix_version_t* abstract data type. + * @brief The definition of the celix_version_t* abstract data type. */ typedef struct celix_version celix_version_t; /** - * Creates a new celix_version_t* using the supplied arguments. + * @brief Create a new celix_version_t* using the supplied arguments. * - * @param major Major component of the version identifier. - * @param minor Minor component of the version identifier. - * @param micro Micro component of the version identifier. - * @param qualifier Qualifier component of the version identifier. If - * null is specified, then the qualifier will be set to + * @param[in] major Major component of the version identifier. + * @param[in] minor Minor component of the version identifier. + * @param[in] micro Micro component of the version identifier. + * @param[in] qualifier Qualifier component of the version identifier. If + * NULL is specified, then the qualifier will be set to * the empty string. * @return The created version or NULL if the input was incorrect */ @@ -47,15 +47,15 @@ celix_version_t* celix_version_createVersion(int major, int minor, int micro, co void celix_version_destroy(celix_version_t* version); /** - * Creates a copy of version. + * @brief Create a copy of version. * - * @param version The version to copy + * @param[in] version The version to copy * @return the copied version */ celix_version_t* celix_version_copy(const celix_version_t* version); /** - * Creates a version identifier from the specified string. + * @brief Create a version identifier from the specified string. * *

* Here is the grammar for version strings. @@ -72,27 +72,50 @@ celix_version_t* celix_version_copy(const celix_version_t* version); * * There must be no whitespace in version. * - * @param versionStr String representation of the version identifier. + * @param[in] versionStr String representation of the version identifier. * @return The created version or NULL if the input was invalid. */ celix_version_t* celix_version_createVersionFromString(const char *versionStr); /** - * The empty version "0.0.0". - * + * @brief Create empty version "0.0.0". */ celix_version_t* celix_version_createEmptyVersion(); +/** + * @brief Gets the major version number of a celix version. + * + * @param[in] version The celix version. + * @return The major version number. + */ int celix_version_getMajor(const celix_version_t* version); +/** + * @brief Gets the minor version number of a celix version. + * + * @param[in] version The celix version. + * @return The minor version number. + */ int celix_version_getMinor(const celix_version_t* version); +/** + * @brief Gets the micro version number of a celix version. + * + * @param[in] version The celix version. + * @return The micro version number. + */ int celix_version_getMicro(const celix_version_t* version); +/** + * @brief Gets the version qualifier of a celix version. + * + * @param[in] version The celix version. + * @return The version qualifier, or NULL if no qualifier is present. + */ const char* celix_version_getQualifier(const celix_version_t* version); /** - * Compares this Version object to another object. + * @brief Compare this Version object to another object. * *

* A version is considered to be less than another version if its @@ -117,12 +140,12 @@ const char* celix_version_getQualifier(const celix_version_t* version); int celix_version_compareTo(const celix_version_t* version, const celix_version_t* compare); /** - * Creates a hash of the version + * @brief Create a hash of the version */ unsigned int celix_version_hash(const celix_version_t* version); /** - * Returns the string representation of version identifier. + * @brief Return the string representation of version identifier. * *

* The format of the version string will be major.minor.micro @@ -136,7 +159,17 @@ unsigned int celix_version_hash(const celix_version_t* version); char* celix_version_toString(const celix_version_t* version); /** - * Check if two versions are semantically compatible. + * @brief Fill a given string with the string representation of the given version. + * + * @param[in] version The version to fill the string with. + * @param[out] str The string to fill. + * @param[in] strLen The length of the string. + * @return true if the string was filled successfully, false otherwise. + */ +bool celix_version_fillString(const celix_version_t* version, char *str, size_t strLen); + +/** + * @brief Check if two versions are semantically compatible. * *

* The user version is compatible with the provider version if the provider version is in the range @@ -149,7 +182,7 @@ char* celix_version_toString(const celix_version_t* version); bool celix_version_isCompatible(const celix_version_t* user, const celix_version_t* provider); /** - * Check if two versions are semantically compatible. + * @brief Check if two versions are semantically compatible. * *

* The user version is compatible with the provider version if the provider version is in the range @@ -163,7 +196,7 @@ bool celix_version_isCompatible(const celix_version_t* user, const celix_version bool celix_version_isUserCompatible(const celix_version_t* user, int providerMajorVersionPart, int provideMinorVersionPart); /** - * Compare a provider celix version is with a provided major and minor version. Ignoring the patch version part. + * @brief Compare a provider celix version is with a provided major and minor version. Ignoring the patch version part. * * @param version The version to compare. * @param majorVersionPart The major version part to compare. diff --git a/libs/utils/private/test/version_test.cpp b/libs/utils/private/test/version_test.cpp deleted file mode 100644 index 1fa59e71a..000000000 --- a/libs/utils/private/test/version_test.cpp +++ /dev/null @@ -1,414 +0,0 @@ -/* - * 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 - -#include "CppUTest/TestHarness.h" -#include "CppUTest/TestHarness_c.h" -#include "CppUTest/CommandLineTestRunner.h" - -#include "celix_version.h" -#include "version.h" -#include "celix_version.h" - -extern "C" -{ -#include "version_private.h" -} - -int main(int argc, char** argv) { - MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); - return RUN_ALL_TESTS(argc, argv); -} - -static char* my_strdup(const char* s){ - if (s == NULL) { - return NULL; - } - - size_t len = strlen(s); - - char *d = (char *) calloc(len + 1, sizeof(char)); - - if (d == NULL) { - return NULL; - } - - strncpy(d,s,len); - return d; -} - -TEST_GROUP(version) { - - void setup(void) { - } - - void teardown() { - } - -}; - - -TEST(version, create) { - version_pt version = NULL; - char * str; - -// str = my_strdup("abc"); -// status = version_createVersion(1, 2, 3, str, &version); -// LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, status); - - str = my_strdup("abc"); - LONGS_EQUAL(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - STRCMP_EQUAL("abc", version->qualifier); - - version_destroy(version); - version = NULL; - LONGS_EQUAL(CELIX_SUCCESS, version_createVersion(1, 2, 3, NULL, &version)); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - STRCMP_EQUAL("", version->qualifier); - - version_destroy(version); - version = NULL; - free(str); - str = my_strdup("abc"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersion(-1, -2, -3, str, &version)); - - version_destroy(version); - version = NULL; - free(str); - str = my_strdup("abc|xyz"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersion(1, 2, 3, str, &version)); - - version_destroy(version); - free(str); -} - -TEST(version, clone) { - version_pt version = NULL, clone = NULL; - char * str; - - str = my_strdup("abc"); - LONGS_EQUAL(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); - LONGS_EQUAL(CELIX_SUCCESS, version_clone(version, &clone)); - CHECK_C(version != NULL); - LONGS_EQUAL(1, clone->major); - LONGS_EQUAL(2, clone->minor); - LONGS_EQUAL(3, clone->micro); - STRCMP_EQUAL("abc", clone->qualifier); - - version_destroy(clone); - version_destroy(version); - free(str); -} - -TEST(version, createFromString) { - version_pt version = NULL; - celix_status_t status = CELIX_SUCCESS; - char * str; - - str = my_strdup("1"); - LONGS_EQUAL(CELIX_SUCCESS, version_createVersionFromString(str, &version)); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - - version_destroy(version); - - free(str); - str = my_strdup("a"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = my_strdup("1.a"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = my_strdup("1.1.a"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = my_strdup("-1"); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = my_strdup("1.2"); - version = NULL; - LONGS_EQUAL(CELIX_SUCCESS, version_createVersionFromString(str, &version)); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - - version_destroy(version); - - free(str); - str = my_strdup("1.2.3"); - version = NULL; - status = version_createVersionFromString(str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - - version_destroy(version); - free(str); - str = my_strdup("1.2.3.abc"); - version = NULL; - status = version_createVersionFromString(str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - STRCMP_EQUAL("abc", version->qualifier); - - version_destroy(version); - free(str); - str = my_strdup("1.2.3.abc_xyz"); - version = NULL; - status = version_createVersionFromString(str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - STRCMP_EQUAL("abc_xyz", version->qualifier); - - version_destroy(version); - free(str); - str = my_strdup("1.2.3.abc-xyz"); - version = NULL; - status = version_createVersionFromString(str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - LONGS_EQUAL(1, version->major); - LONGS_EQUAL(2, version->minor); - LONGS_EQUAL(3, version->micro); - STRCMP_EQUAL("abc-xyz", version->qualifier); - - version_destroy(version); - free(str); - str = my_strdup("1.2.3.abc|xyz"); - status = version_createVersionFromString(str, &version); - LONGS_EQUAL(CELIX_ILLEGAL_ARGUMENT, status); - - free(str); -} - -TEST(version, createEmptyVersion) { - version_pt version = NULL; - celix_status_t status = CELIX_SUCCESS; - - status = version_createEmptyVersion(&version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - LONGS_EQUAL(0, version->major); - LONGS_EQUAL(0, version->minor); - LONGS_EQUAL(0, version->micro); - STRCMP_EQUAL("", version->qualifier); - - version_destroy(version); -} - -TEST(version, getters) { - version_pt version = NULL; - celix_status_t status = CELIX_SUCCESS; - char * str; - int major, minor, micro; - const char *qualifier; - - str = my_strdup("abc"); - status = version_createVersion(1, 2, 3, str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - - version_getMajor(version, &major); - LONGS_EQUAL(1, major); - - version_getMinor(version, &minor); - LONGS_EQUAL(2, minor); - - version_getMicro(version, µ); - LONGS_EQUAL(3, micro); - - version_getQualifier(version, &qualifier); - STRCMP_EQUAL("abc", qualifier); - - version_destroy(version); - free(str); -} - -TEST(version, compare) { - version_pt version = NULL, compare = NULL; - celix_status_t status = CELIX_SUCCESS; - char * str; - int result; - - // Base version to compare - str = my_strdup("abc"); - status = version_createVersion(1, 2, 3, str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - - // Compare equality - free(str); - str = my_strdup("abc"); - compare = NULL; - status = version_createVersion(1, 2, 3, str, &compare); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - status = version_compareTo(version, compare, &result); - LONGS_EQUAL(CELIX_SUCCESS, status); - LONGS_EQUAL(0, result); - - // Compare against a higher version - free(str); - str = my_strdup("bcd"); - version_destroy(compare); - compare = NULL; - status = version_createVersion(1, 2, 3, str, &compare); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - status = version_compareTo(version, compare, &result); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK(result < 0); - - // Compare againts a lower version - free(str); - str = my_strdup("abc"); - version_destroy(compare); - compare = NULL; - status = version_createVersion(1, 1, 3, str, &compare); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - status = version_compareTo(version, compare, &result); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK(result > 0); - - version_destroy(compare); - version_destroy(version); - free(str); -} - -TEST(version, celix_version_compareToMajorMinor) { - celix_version_t *version1 = celix_version_createVersion(2, 2, 0, nullptr); - celix_version_t *version2 = celix_version_createVersion(2, 2, 4, "qualifier"); - - CHECK_EQUAL(0, celix_version_compareToMajorMinor(version1, 2, 2)); - CHECK_EQUAL(0, celix_version_compareToMajorMinor(version2, 2, 2)); - - CHECK_TRUE(celix_version_compareToMajorMinor(version1, 2, 3) < 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version2, 2, 3) < 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version1, 3, 3) < 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version2, 3, 3) < 0); - - - CHECK_TRUE(celix_version_compareToMajorMinor(version1, 2, 1) > 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version2, 2, 1) > 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version1, 1, 1) > 0); - CHECK_TRUE(celix_version_compareToMajorMinor(version2, 1, 1) > 0); - - celix_version_destroy(version1); - celix_version_destroy(version2); -} - -TEST(version, toString) { - version_pt version = NULL; - celix_status_t status = CELIX_SUCCESS; - char * str; - char *result = NULL; - - str = my_strdup("abc"); - status = version_createVersion(1, 2, 3, str, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - - status = version_toString(version, &result); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(result != NULL); - STRCMP_EQUAL("1.2.3.abc", result); - free(result); - - version_destroy(version); - version = NULL; - status = version_createVersion(1, 2, 3, NULL, &version); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(version != NULL); - - status = version_toString(version, &result); - LONGS_EQUAL(CELIX_SUCCESS, status); - CHECK_C(result != NULL); - STRCMP_EQUAL("1.2.3", result); - - version_destroy(version); - free(result); - free(str); -} - -TEST(version,semanticCompatibility) { - version_pt provider = NULL; - version_pt compatible_user = NULL; - version_pt incompatible_user_by_major = NULL; - version_pt incompatible_user_by_minor = NULL; - celix_status_t status = CELIX_SUCCESS; - bool isCompatible = false; - - status = version_isCompatible(compatible_user, provider, &isCompatible); - LONGS_EQUAL(CELIX_SUCCESS, status); - - version_createVersion(2, 3, 5, NULL, &provider); - version_createVersion(2, 1, 9, NULL, &compatible_user); - version_createVersion(1, 3, 5, NULL, &incompatible_user_by_major); - version_createVersion(2, 5, 7, NULL, &incompatible_user_by_minor); - - status = version_isCompatible(compatible_user, provider, &isCompatible); - CHECK(isCompatible == true); - LONGS_EQUAL(CELIX_SUCCESS, status); - - status = version_isCompatible(incompatible_user_by_major, provider, &isCompatible); - CHECK(isCompatible == false); - LONGS_EQUAL(CELIX_SUCCESS, status); - - status = version_isCompatible(incompatible_user_by_minor, provider, &isCompatible); - CHECK(isCompatible == false); - LONGS_EQUAL(CELIX_SUCCESS, status); - - version_destroy(provider); - version_destroy(compatible_user); - version_destroy(incompatible_user_by_major); - version_destroy(incompatible_user_by_minor); -} - -TEST(version, compareEmptyAndNullQualifier) { - //nullptr or "" qualifier should be the same - auto* v1 = celix_version_createVersion(0, 0, 0, nullptr); - auto* v2 = celix_version_createVersion(0, 0, 0, ""); - CHECK_EQUAL(0, celix_version_compareTo(v1, v1)); - CHECK_EQUAL(0, celix_version_compareTo(v1, v2)); - CHECK_EQUAL(0, celix_version_compareTo(v2, v2)); - - celix_version_destroy(v1); - celix_version_destroy(v2); -} \ No newline at end of file diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index a83211248..af8b892f1 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "celix_build_assert.h" #include "utils.h" //TODO try to remove @@ -33,18 +34,37 @@ #include "celix_string_hash_map.h" -#define PROPERTIES_INITIAL_HASHMAP_CAPACITY 10 -#define SHORT_PROPERTIES_OPTIMIZATION_SIZE 1024 +#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 1024 +#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16 + +static const char* const CELIX_PROPERTIES_BOOL_TRUE_STRVAL = "true"; +static const char* const CELIX_PROPERTIES_BOOL_FALSE_STRVAL = "false"; struct celix_properties { celix_string_hash_map_t* map; /** - * buffer used to store the first key/value entries so that no additional memory allocation is needed. + * String buffer used to store the first key/value entries, so that in many cases additional memory allocation + * can be prevented. */ - char buffer[SHORT_PROPERTIES_OPTIMIZATION_SIZE]; -}; + char stringBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE]; + + /** + * The current string buffer index. + */ + int currentStringBufferIndex; + + /** + * Entries buffer used to store the first entries, so that in many cases additional memory allocation + * can be prevented. + */ + celix_properties_entry_t entriesBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE]; + /** + * The current string buffer index. + */ + int currentEntriesBufferIndex; +}; #define MALLOC_BLOCK_SIZE 5 @@ -75,7 +95,7 @@ properties_pt properties_loadFromString(const char *input){ * Header is ignored for now, cannot handle comments yet */ void properties_store(properties_pt properties, const char* filename, const char* header) { - return celix_properties_store(properties, filename, header); + celix_properties_store(properties, filename, header); } celix_status_t properties_copy(properties_pt properties, properties_pt *out) { @@ -217,19 +237,187 @@ static void parseLine(const char* line, celix_properties_t *props) { } +/** + * Create a new string from the provided str by either using strup or storing the string the short properties + * optimization string buffer. + */ +static char* celix_properties_createString(celix_properties_t* properties, const char* str) { + size_t len = str == NULL ? 0 : strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; + size_t left = CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - properties->currentStringBufferIndex; + char* result; + if (len < left) { + memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, len); + result = &properties->stringBuffer[properties->currentStringBufferIndex]; + properties->currentStringBufferIndex += (int)len; + } else { + result = celix_utils_strdup(str); + } + return result; +} + +/** + * Fill entry and optional use the short properties optimization string buffer. + */ +static celix_status_t celix_properties_fillEntry( + celix_properties_t *properties, + celix_properties_entry_t* entry, + const char *key, + const char *strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + const celix_version_t* versionValue) { + char convertedValueBuffer[32]; + entry->key = celix_properties_createString(properties, key); + if (strValue != NULL) { + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + entry->value = celix_properties_createString(properties, strValue); + entry->typed.strValue = entry->value; + } else if (longValue != NULL) { + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; + entry->typed.longValue = *longValue; + int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue); + if (written < 0 || written >= sizeof(convertedValueBuffer)) { + entry->value = celix_properties_createString(properties, convertedValueBuffer); + } else { + char* val = NULL; + asprintf(&val, "%li", entry->typed.longValue); + entry->value = val; + } + } else if (doubleValue != NULL) { + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; + entry->typed.doubleValue = *doubleValue; + int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue); + if (written < 0 || written >= sizeof(convertedValueBuffer)) { + entry->value = celix_properties_createString(properties, convertedValueBuffer); + } else { + char* val = NULL; + asprintf(&val, "%f", entry->typed.doubleValue); + entry->value = val; + } + } else if (boolValue != NULL) { + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL; + entry->typed.boolValue = *boolValue; + entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL; + } else /*versionValue*/ { + assert(versionValue != NULL); + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; + entry->typed.versionValue = celix_version_copy(versionValue); + + bool written = celix_version_fillString(versionValue, convertedValueBuffer, sizeof(convertedValueBuffer)); + if (written) { + entry->value = celix_properties_createString(properties, convertedValueBuffer); + } else { + entry->value = celix_version_toString(versionValue); + } + } + if (entry->key == NULL || entry->value == NULL) { + return CELIX_ENOMEM; + } + return CELIX_SUCCESS; +} + +/** + * Allocate entry and optionally use the short properties optimization entries buffer. + */ +static celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties) { + celix_properties_entry_t* entry; + if (properties->currentEntriesBufferIndex < CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) { + entry = &properties->entriesBuffer[properties->currentEntriesBufferIndex++]; + } else { + entry = malloc(sizeof(*entry)); + } + return entry; +} + +/** + * Create entry and optionally use the short properties optimization entries buffer and take ownership of the + * provided key and value strings. + */ +static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_properties_t *properties, + char *key, + char *strValue) { + celix_properties_entry_t* entry = celix_properties_allocEntry(properties); + if (entry == NULL) { + return NULL; + } + entry->key = key; + entry->value = strValue; + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + entry->typed.strValue = strValue; + return entry; +} + + +/** + * Create entry and optionally use the short properties optimization buffers. + * Only 1 of the types values (strValue, LongValue, etc) should be provided. + */ +static celix_properties_entry_t* celix_properties_createEntry( + celix_properties_t *properties, + const char *key, + const char *strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + const celix_version_t* versionValue) { + celix_properties_entry_t* entry = celix_properties_allocEntry(properties); + if (entry == NULL) { + return NULL; + } + + celix_status_t status = celix_properties_fillEntry(properties, entry, key, strValue, longValue, doubleValue, + boolValue, versionValue); + if (status != CELIX_SUCCESS) { + free(entry); + entry = NULL; + } + return entry; +} + +/** + * Create and add entry and optionally use the short properties optimization buffers. + * Only 1 of the types values (strValue, LongValue, etc) should be provided. + */ +static void celix_properties_createAndSetEntry( + celix_properties_t *properties, + const char *key, + const char *strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + const celix_version_t* versionValue) { + celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, + boolValue, versionValue); + if (entry != NULL) { + celix_stringHashMap_put(properties->map, key, entry); + } +} + -/********************************************************************************************************************** - ********************************************************************************************************************** - * Updated API - ********************************************************************************************************************** - **********************************************************************************************************************/ +static void celix_properties_freeString(celix_properties_t* properties, char* str) { + if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == CELIX_PROPERTIES_BOOL_FALSE_STRVAL) { + //str is static const char* const -> nop + } else if (str >= properties->stringBuffer && + str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { + //str is part of the properties string buffer -> nop + } else { + free(str); + } +} -static void celix_properties_removeEntryCallback(void* handle, const char* key, celix_hash_map_value_t val) { - //celix_properties_t* props = handle; - //TODO add check of short properties optimization buffer - free((char*)key); - free(val.ptrValue); +static void celix_properties_removeEntryCallback(void* handle, const char* key __attribute__((unused)), celix_hash_map_value_t val) { + celix_properties_t* properties = handle; + celix_properties_entry_t* entry = val.ptrValue; + celix_properties_freeString(properties, (char*)entry->key); + celix_properties_freeString(properties, (char*)entry->value); + if (entry >= properties->entriesBuffer && + entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { + //entry is part of the properties entries buffer -> nop. + } else { + free(entry); + } } @@ -238,10 +426,12 @@ celix_properties_t* celix_properties_create(void) { if (props != NULL) { celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; opts.storeKeysWeakly = true; - opts.initialCapacity = PROPERTIES_INITIAL_HASHMAP_CAPACITY; + opts.initialCapacity = CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE; opts.removedCallbackData = props; opts.removedCallback = celix_properties_removeEntryCallback; props->map = celix_stringHashMap_createWithOptions(&opts); + props->currentStringBufferIndex = 0; + props->currentEntriesBufferIndex = 0; } return props; } @@ -329,32 +519,47 @@ celix_properties_t* celix_properties_loadFromString(const char *input) { /** * @brief Store properties string to file and escape the characters '#', '!', '=' and ':' if encountered. */ -static void celix_properties_storeEscapedString(FILE* file, const char* str) { +static int celix_properties_storeEscapedString(FILE* file, const char* str) { + int rc = 0; for (int i = 0; i < strlen(str); i += 1) { if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') { - fputc('\\', file); + rc = fputc('\\', file); + if (rc == EOF) { + break; + } } - fputc(str[i], file); + rc = fputc(str[i], file); } + return rc; } -void celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { +celix_status_t celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { FILE *file = fopen (filename, "w+" ); if (file == NULL) { - perror("File is null"); - return; + return CELIX_FILE_IO_EXCEPTION; } + int rc = 0; CELIX_STRING_HASH_MAP_ITERATE(properties->map, iter) { const char* val = iter.value.ptrValue; - celix_properties_storeEscapedString(file, iter.key); - fputc('=', file); - celix_properties_storeEscapedString(file, val); - fputc('\n', file); - + if (rc != EOF) { + rc = celix_properties_storeEscapedString(file, iter.key); + } + if (rc != EOF) { + rc = fputc('=', file); + } + if (rc != EOF) { + rc = celix_properties_storeEscapedString(file, val); + } + if (rc != EOF) { + rc = fputc('\n', file); + } } - fclose(file); + if (rc != EOF) { + rc = fclose(file); + } + return rc != EOF ? CELIX_SUCCESS : CELIX_FILE_IO_EXCEPTION; } celix_properties_t* celix_properties_copy(const celix_properties_t *properties) { @@ -363,30 +568,55 @@ celix_properties_t* celix_properties_copy(const celix_properties_t *properties) return copy; } - CELIX_STRING_HASH_MAP_ITERATE(properties->map, iter) { - celix_properties_set(copy, iter.key, iter.value.ptrValue); + CELIX_PROPERTIES_ITERATE(properties, iter) { + if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { + celix_properties_set(copy, iter.entry.key, iter.entry.value); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { + celix_properties_setLong(copy, iter.entry.key, iter.entry.typed.longValue); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { + celix_properties_setDouble(copy, iter.entry.key, iter.entry.typed.doubleValue); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { + celix_properties_setBool(copy, iter.entry.key, iter.entry.typed.boolValue); + } else /*version*/ { + assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION); + celix_properties_setVersion(copy, iter.entry.key, iter.entry.typed.versionValue); + } } return copy; } +celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key) { + celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); + return entry == NULL ? CELIX_PROPERTIES_VALUE_TYPE_UNSET : entry->valueType; +} + const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { - const char* value = NULL; + celix_properties_entry_t* entry = NULL; if (properties != NULL) { - value = celix_stringHashMap_get(properties->map, key); + entry = celix_stringHashMap_get(properties->map, key); } - return value == NULL ? defaultValue : value; + return entry == NULL ? defaultValue : entry->value; +} + +celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* properties, const char* key) { + celix_properties_entry_t invalidEntry; + memset(&invalidEntry, 0, sizeof(invalidEntry)); + invalidEntry.valueType = CELIX_PROPERTIES_VALUE_TYPE_UNSET; + celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); + return entry == NULL ? invalidEntry : *entry; } void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { - if (properties != NULL) { - celix_stringHashMap_put(properties->map, celix_utils_strdup(key), celix_utils_strdup(value)); + if (properties != NULL && key != NULL && value != NULL) { + celix_properties_createAndSetEntry(properties, key, value, NULL, NULL, NULL, NULL); } } void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { - if (properties != NULL) { - if (properties != NULL) { - celix_stringHashMap_put(properties->map, key, value); + if (properties != NULL && key != NULL && value != NULL) { + celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, key, value); + if (entry != NULL) { + celix_stringHashMap_put(properties->map, entry->key, entry); } } } @@ -399,12 +629,14 @@ void celix_properties_unset(celix_properties_t *properties, const char *key) { long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue) { long result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { + celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { + return entry->typed.longValue; + } else if (entry != NULL) { char *enptr = NULL; errno = 0; - long r = strtol(val, &enptr, 10); - if (enptr != val && errno == 0) { + long r = strtol(entry->value, &enptr, 10); + if (enptr != entry->value && errno == 0) { result = r; } } @@ -412,23 +644,19 @@ long celix_properties_getAsLong(const celix_properties_t *props, const char *key } void celix_properties_setLong(celix_properties_t *props, const char *key, long value) { - char buf[32]; //should be enough to store long long int - int writen = snprintf(buf, 32, "%li", value); - if (writen <= 31) { - celix_properties_set(props, key, buf); - } else { - fprintf(stderr,"buf to small for value '%li'\n", value); - } + celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL); } double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { double result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { + celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { + return entry->typed.doubleValue; + } else if (entry != NULL) { char *enptr = NULL; errno = 0; - double r = strtod(val, &enptr); - if (enptr != val && errno == 0) { + double r = strtod(entry->value, &enptr); + if (enptr != entry->value && errno == 0) { result = r; } } @@ -436,21 +664,17 @@ double celix_properties_getAsDouble(const celix_properties_t *props, const char } void celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { - char buf[32]; //should be enough to store long long int - int writen = snprintf(buf, 32, "%f", val); - if (writen <= 31) { - celix_properties_set(props, key, buf); - } else { - fprintf(stderr,"buf to small for value '%f'\n", val); - } + celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL); } bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { bool result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { + celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { + return entry->typed.boolValue; + } else if (entry != NULL) { char buf[32]; - snprintf(buf, 32, "%s", val); + snprintf(buf, 32, "%s", entry->value); char *trimmed = utils_stringTrim(buf); if (strncasecmp("true", trimmed, strlen("true")) == 0) { result = true; @@ -462,9 +686,29 @@ bool celix_properties_getAsBool(const celix_properties_t *props, const char *key } void celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { - celix_properties_set(props, key, val ? "true" : "false"); + celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); +} + +const celix_version_t* celix_properties_getAsVersion( + const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue) { + celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + return entry->typed.versionValue; + } else if (entry != NULL) { + //NOTE not converting to version, due to ownership + //TODO improve? + return NULL; + } + return NULL; } +void celix_properties_setVersion(celix_properties_t *props, const char *key, const celix_version_t* version) { + celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, NULL, version); +} + + int celix_properties_size(const celix_properties_t *properties) { return (int)celix_stringHashMap_size(properties->map); } @@ -475,12 +719,15 @@ typedef struct { } celix_properties_iterator_internal_t; celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties) { - CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); celix_properties_iterator_internal_t internalIter; internalIter.mapIter = celix_stringHashMap_begin(properties->map); internalIter.props = properties; celix_properties_iterator_t iter; + iter.index = -1; + memset(&iter.entry, 0, sizeof(iter.entry)); + + CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(iter._data)); memset(&iter._data, 0, sizeof(iter._data)); memcpy(iter._data, &internalIter, sizeof(internalIter)); return iter; @@ -488,22 +735,63 @@ celix_properties_iterator_t celix_propertiesIterator_construct(const celix_prope bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) { celix_properties_iterator_internal_t internalIter; - memcpy(&internalIter, iter, sizeof(internalIter)); + memcpy(&internalIter, iter->_data, sizeof(internalIter)); + //celix_stringHashMapIterator_next(&internalIter.mapIter); return !celix_stringHashMapIterator_isEnd(&internalIter.mapIter); } const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) { celix_properties_iterator_internal_t internalIter; - memcpy(&internalIter, iter, sizeof(internalIter)); + memcpy(&internalIter, iter->_data, sizeof(internalIter)); //note assigning key first and then move the next, because celix string hash map iter start at the beginning const char* key = internalIter.mapIter.key; + iter->index = (int)internalIter.mapIter.index; + celix_properties_entry_t* entry = internalIter.mapIter.value.ptrValue; + if (entry != NULL) { + memcpy(&iter->entry, iter, sizeof(iter->entry)); + } else { + memset(&iter->entry, 0, sizeof(iter->entry)); + } celix_stringHashMapIterator_next(&internalIter.mapIter); - memcpy(iter, &internalIter, sizeof(internalIter)); + memcpy(iter->_data, &internalIter, sizeof(internalIter)); return key; } +celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties) { + CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); + celix_properties_iterator_internal_t internalIter; + internalIter.mapIter = celix_stringHashMap_begin(properties->map); + internalIter.props = properties; + + celix_properties_iterator_t iter; + iter.index = 0; + memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); + + memset(&iter._data, 0, sizeof(iter._data)); + memcpy(iter._data, &internalIter, sizeof(internalIter)); + return iter; +} + +void celix_propertiesIterator_next(celix_properties_iterator_t *iter) { + celix_properties_iterator_internal_t internalIter; + memcpy(&internalIter, iter->_data, sizeof(internalIter)); + celix_stringHashMapIterator_next(&internalIter.mapIter); + memcpy(iter->_data, &internalIter, sizeof(internalIter)); + if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { + memset(&iter->entry, 0, sizeof(iter->entry)); + } else { + memcpy(&iter->entry, internalIter.mapIter.value.ptrValue, sizeof(iter->entry)); + } +} + +bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter) { + celix_properties_iterator_internal_t internalIter; + memcpy(&internalIter, iter->_data, sizeof(internalIter)); + return celix_stringHashMapIterator_isEnd(&internalIter.mapIter); +} + bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b) { celix_properties_iterator_internal_t internalIterA; memcpy(&internalIterA, a, sizeof(internalIterA)); @@ -513,10 +801,7 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const internalIterA.mapIter.key == internalIterB.mapIter.key; } - -//TODO can the return be const? -//TODO and can this be removed -> look into C++ PropetiesIter -celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter) { +celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter) { celix_properties_iterator_internal_t internalIter; memcpy(&internalIter, iter, sizeof(internalIter)); return (celix_properties_t*)internalIter.props; diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index f67bacc1d..f83fe6555 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -286,6 +286,15 @@ char* celix_version_toString(const celix_version_t* version) { return string; } +bool celix_version_fillString(const celix_version_t* version, char *str, size_t strLen) { + int written; + if (strnlen(version->qualifier, 1) > 0) { + written = snprintf(str, strLen, "%d.%d.%d.%s", version->major, version->minor, version->micro, version->qualifier); + } else { + written = snprintf(str, strLen, "%d.%d.%d", version->major, version->minor, version->micro); + } + return written >= 0 && written < strLen; +} bool celix_version_isCompatible(const celix_version_t* user, const celix_version_t* provider) { if (user == NULL && provider == NULL) { From 02b08a110d221aab7f65bf14b3af7d4d48692fb0 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 3 Jan 2023 22:55:24 +0100 Subject: [PATCH 04/63] Improve celix properties iterator --- libs/utils/gtest/src/PropertiesTestSuite.cc | 61 +++++++- libs/utils/include/celix/Properties.h | 26 ++-- libs/utils/include/celix_long_hash_map.h | 9 +- libs/utils/include/celix_properties.h | 145 ++++++++++---------- libs/utils/include/celix_string_hash_map.h | 6 +- libs/utils/src/properties.c | 15 +- 6 files changed, 158 insertions(+), 104 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index b29802823..1528ddb27 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -233,6 +233,20 @@ TEST_F(PropertiesTestSuite, boolTest) { celix_properties_destroy(properties); } +TEST_F(PropertiesTestSuite, fillTest) { + celix_properties_t *props = celix_properties_create(); + int testCount = 1000; + for (int i = 0; i < 1000; ++i) { + char k[5]; + snprintf(k, sizeof(k), "%i", i); + const char* v = "a"; + celix_properties_set(props, k, v); + } + EXPECT_EQ(celix_properties_size(props), testCount); + celix_properties_destroy(props); +} + + TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { celix_properties_t *props = celix_properties_create(); EXPECT_EQ(0, celix_properties_size(props)); @@ -274,12 +288,12 @@ TEST_F(PropertiesTestSuite, getType) { } TEST_F(PropertiesTestSuite, getEntry) { - auto *props = celix_properties_create(); + auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_setLong(props, "key2", 123); celix_properties_setDouble(props, "key3", 123.456); celix_properties_setBool(props, "key4", true); - auto *version = celix_version_createVersion(1, 2, 3, nullptr); + auto* version = celix_version_createVersion(1, 2, 3, nullptr); celix_properties_setVersion(props, "key5", version); auto entry = celix_properties_getEntry(props, "key1"); @@ -316,7 +330,7 @@ TEST_F(PropertiesTestSuite, getEntry) { } TEST_F(PropertiesTestSuite, iteratorNextKey) { - auto *props = celix_properties_create(); + auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); celix_properties_set(props, "key3", "value3"); @@ -336,7 +350,7 @@ TEST_F(PropertiesTestSuite, iteratorNextKey) { } TEST_F(PropertiesTestSuite, iteratorNext) { - auto *props = celix_properties_create(); + auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); celix_properties_set(props, "key3", "value3"); @@ -357,7 +371,7 @@ TEST_F(PropertiesTestSuite, iteratorNext) { } TEST_F(PropertiesTestSuite, iterateOverProperties) { - celix_properties_t* props = celix_properties_create(); + auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); @@ -378,4 +392,41 @@ TEST_F(PropertiesTestSuite, iterateOverProperties) { EXPECT_EQ(innerCount, 4); celix_properties_destroy(props); + + + props = celix_properties_create(); + int count = 0; + CELIX_PROPERTIES_ITERATE(props, outerIter) { + count++; + } + EXPECT_EQ(count, 0); + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, getAsVersion) { + auto* properties = celix_properties_create(); + + // Test getting a version property + auto* expected = celix_version_createVersion(1, 2, 3, "test"); + celix_properties_setVersion(properties, "key", expected); + const auto* actual = celix_properties_getAsVersion(properties, "key", nullptr); + EXPECT_EQ(celix_version_getMajor(expected), celix_version_getMajor(actual)); + EXPECT_EQ(celix_version_getMinor(expected), celix_version_getMinor(actual)); + EXPECT_EQ(celix_version_getMicro(expected), celix_version_getMicro(actual)); + EXPECT_STREQ(celix_version_getQualifier(expected), celix_version_getQualifier(actual)); + + // Test getting a non-version property + celix_properties_set(properties, "key2", "value"); + auto* emptyVersion = celix_version_createEmptyVersion(); + actual = celix_properties_getAsVersion(properties, "key2", emptyVersion); + EXPECT_EQ(celix_version_getMajor(actual), 0); + EXPECT_EQ(celix_version_getMinor(actual), 0); + EXPECT_EQ(celix_version_getMicro(actual), 0); + EXPECT_STREQ(celix_version_getQualifier(actual), ""); + + EXPECT_EQ(celix_properties_getAsVersion(properties, "non-existent", nullptr), nullptr); + + celix_version_destroy(expected); + celix_version_destroy(emptyVersion); + celix_properties_destroy(properties); } \ No newline at end of file diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index da6536168..111c7fe47 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -34,14 +34,8 @@ namespace celix { */ class PropertiesIterator { public: - explicit PropertiesIterator(celix_properties_t* props) { - iter = celix_propertiesIterator_construct(props); - next(); - } - explicit PropertiesIterator(const celix_properties_t* props) { - iter = celix_propertiesIterator_construct(props); - next(); + iter = celix_properties_begin(props); } PropertiesIterator& operator++() { @@ -65,17 +59,17 @@ namespace celix { } void next() { - if (celix_propertiesIterator_hasNext(&iter)) { - auto* k = celix_propertiesIterator_nextKey(&iter); - auto* props = celix_propertiesIterator_properties(&iter); - auto *v = celix_properties_get(props, k, ""); - first = std::string{(const char*)k}; - second = std::string{(const char*)v}; - } else { + celix_propertiesIterator_next(&iter); + if (celix_propertiesIterator_isEnd(&iter)) { moveToEnd(); + } else { + first = iter.entry.key; + second = iter.entry.value; + end = false; } } + //TODO try to remove moveToEnd void moveToEnd() { first = {}; second = {}; @@ -85,6 +79,7 @@ namespace celix { //TODO for C++17 try to update first and second to stringview std::string first{}; std::string second{}; + //TODO iter? private: celix_properties_iterator_t iter{.index = -1, .entry = {}, ._data = {}}; bool end{false}; @@ -135,6 +130,8 @@ namespace celix { } #endif + //TODO operator= with long, double, boolean and version + [[nodiscard]] const char* getValue() const { if (charKey == nullptr) { return celix_properties_get(props.get(), stringKey.c_str(), nullptr); @@ -306,6 +303,7 @@ namespace celix { std::string_view view{value}; celix_properties_set(cProps.get(), key.data(), view.data()); } else { + //TODO spit up to use setLong, setBool, setDouble and setVersion using namespace std; celix_properties_set(cProps.get(), key.data(), to_string(value).c_str()); } diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index c704ab602..50bf810d3 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -261,8 +261,10 @@ bool celix_longHashMap_remove(celix_long_hash_map_t* map, long key); void celix_longHashMap_clear(celix_long_hash_map_t* map); /** - * @brief Create and return a hash map iterator for the beginning of the hash map. + * @brief Returns an iterator pointing to the first element in the map. * + * @param map The map to get the iterator for. + * @return An iterator pointing to the first element in the map. */ celix_long_hash_map_iterator_t celix_longHashMap_begin(const celix_long_hash_map_t* map); @@ -284,7 +286,7 @@ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); * @brief Marco to loop over all the entries of a long hash map. * * Small example of how to use the iterate macro: - * @code + * @code{.c} * celix_long_hash_map_t* map = ... * CELIX_LONG_HASH_MAP_ITERATE(map, iter) { * printf("Visiting hash map entry with key %li\n", inter.key); @@ -294,7 +296,6 @@ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); * @param map The (const celix_long_hash_map_t*) map to iterate over. * @param iterName A iterName which will be of type celix_long_hash_map_iterator_t to hold the iterator. */ -//TODO test if the macro can be used nested #define CELIX_LONG_HASH_MAP_ITERATE(map, iterName) \ for (celix_long_hash_map_iterator_t iterName = celix_longHashMap_begin(map); !celix_longHashMapIterator_isEnd(&(iterName)); celix_longHashMapIterator_next(&(iterName))) @@ -302,7 +303,7 @@ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); * @brief Remove the hash map entry for the provided iterator and updates the iterator to the next hash map entry * * Small example of how to use the celix_longHashMapIterator_remove function: - * @code + * @code{.c} * //remove all even entries * celix_long_hash_map_t* map = ... * celix_long_hash_map_iterator_t iter = celix_longHashMap_begin(map); diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 3c8766978..45846506c 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -43,6 +43,8 @@ extern "C" { /** * @brief celix_properties_t is a type that represents a set of key-value pairs called properties, * which can be used to store configuration data or metadata for a services, components or framework configuration. + * + * @note Not thread safe. */ typedef struct celix_properties celix_properties_t; @@ -293,15 +295,13 @@ void celix_properties_setVersion(celix_properties_t* properties, const char* key /** * @brief Gets the value of a property as a Celix version. * - * //TODO, maybe improve, now returns NULL if underlining type is not version + * This function does not convert a string property value to a Celix version automatically. * - * @param properties The property set to search. - * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set, the value is not a Celix version, or if - * the value cannot be converted to a Celix version. - * @return The value of the property as a Celix version, or the default value if the property is not set, - * the value is not a Celix version, or if the value cannot be converted to a Celix version. - * If the value is a string, it will be verified as a valid Celix version. + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value is not a Celix version. + * @return The value of the property if it is a Celix version, or the default value if the property is not set or the + * value is not a Celix version. */ const celix_version_t* celix_properties_getAsVersion( const celix_properties_t* properties, @@ -316,41 +316,6 @@ const celix_version_t* celix_properties_getAsVersion( */ int celix_properties_size(const celix_properties_t* properties); -/** - * @brief Constructs a new properties iterator. - * - * Note: The iterator is initialized to be before the first entry. To advance to the first entry, - * call `celix_propertiesIterator_nextEntry`. - * - * //TODO deprecate? - * - * @param[in] properties The properties object to iterate over. - * @return The newly constructed iterator. - */ -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties); - -/** - * @brief Determines whether the iterator has more entries. - * - * //TODO deprecate? - * - * @param[in] iter The iterator. - * @return true if the iterator has more entries, false otherwise. - */ -bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); - -/** - * @brief Advances the iterator to the next entry and returns the key for the current entry. - * - * If the iterator has no more entries, this function returns NULL. - * - * //TODO deprecate? - * - * @param[in, out] iter The iterator. - * @return The key for the current entry, or NULL if the iterator has no more entries. - */ -const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter); - /** * @brief Constructs an iterator pointing to the first entry in the properties object. * @@ -393,13 +358,76 @@ celix_properties_t* celix_propertiesIterator_properties(const celix_properties_i */ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); +/** + * @brief Iterates over the entries in the specified celix_properties_t object. + * + * This macro allows you to easily iterate over the entries in a celix_properties_t object. + * The loop variable `iterName` will be of type celix_properties_iterator_t and will contain the current + * entry during each iteration. + * + * @param map The properties object to iterate over. + * @param iterName The name of the iterator variable to use in the loop. + * + * Example usage: + * @code{.c} + * // Iterate over all entries in the properties object + * CELIX_PROPERTIES_ITERATE(properties, iter) { + * // Print the key and value of the current entry + * printf("%s: %s\n", iter.entry.key, iter.entry.value); + * } + * @endcode + */ +#define CELIX_PROPERTIES_ITERATE(map, iterName) \ + for (celix_properties_iterator_t iterName = celix_properties_begin(map); \ + !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) + + + + +/**** Deprecated API *************************************************************************************************/ + +/** + * @brief Constructs a new properties iterator. + * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. + * + * Note: The iterator is initialized to be before the first entry. To advance to the first entry, + * call `celix_propertiesIterator_nextEntry`. + * + * @param[in] properties The properties object to iterate over. + * @return The newly constructed iterator. + */ +celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties) + __attribute__((deprecated("celix_propertiesIterator_construct is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); + +/** + * @brief Determines whether the iterator has more entries. + * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. + * + * @param[in] iter The iterator. + * @return true if the iterator has more entries, false otherwise. + */ +bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) + __attribute__((deprecated("celix_propertiesIterator_hasNext is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); + +/** + * @brief Advances the iterator to the next entry and returns the key for the current entry. + * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. + * + * If the iterator has no more entries, this function returns NULL. + * + * @param[in, out] iter The iterator. + * @return The key for the current entry, or NULL if the iterator has no more entries. + */ +const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter) + __attribute__((deprecated("celix_propertiesIterator_nextKey is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); + /** * @brief Macro for iterating over the properties in a property set. + * @deprecated This macro is deprecated, use CELIX_PROPERTIES_ITERATE instead. * * @param[in] properties The property set to iterate over. * @param[out] key The variable to use for the current key in the loop. * - * //TODO deprecate * * Example usage: * @code{.c} @@ -421,40 +449,11 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const * key3 = value3 * @endcode */ -#define CELIX_PROPERTIES_FOR_EACH(map, keyName) \ - for (celix_properties_iterator_t iter_##keyName = celix_properties_begin(map); \ - (keyName) = iter_##keyName.entry.key, !celix_propertiesIterator_isEnd(&(iter_##keyName)); \ - celix_propertiesIterator_next(&(iter_##keyName))) - -/* #define CELIX_PROPERTIES_FOR_EACH(properties, key) \ for(celix_properties_iterator_t iter_##key = celix_propertiesIterator_construct(properties); \ celix_propertiesIterator_hasNext(&iter_##key), (key) = celix_propertiesIterator_nextKey(&iter_##key);) -*/ -/** - * @brief Iterates over the entries in the specified celix_properties_t object. - * - * This macro allows you to easily iterate over the entries in a celix_properties_t object. - * The loop variable `iterName` will be of type celix_properties_iterator_t and will contain the current - * entry during each iteration. - * - * @param map The properties object to iterate over. - * @param iterName The name of the iterator variable to use in the loop. - * - * Example usage: - * @code{.c} - * // Iterate over all entries in the properties object - * CELIX_PROPERTIES_ITERATE(properties, iter) { - * // Print the key and value of the current entry - * printf("%s: %s\n", iter.entry.key, iter.entry.value); - * } - * @endcode - */ -#define CELIX_PROPERTIES_ITERATE(map, iterName) \ - for (celix_properties_iterator_t iterName = celix_properties_begin(map); \ - !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) #ifdef __cplusplus } diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 97c1735b6..1e5fbe828 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -270,7 +270,10 @@ bool celix_stringHashMap_remove(celix_string_hash_map_t* map, const char* key); void celix_stringHashMap_clear(celix_string_hash_map_t* map); /** - * @brief Create and return a hash map iterator for the beginning of the hash map. + * @brief Returns an iterator pointing to the first element in the map. + * + * @param map The map to get the iterator for. + * @return An iterator pointing to the first element in the map. */ celix_string_hash_map_iterator_t celix_stringHashMap_begin(const celix_string_hash_map_t* map); @@ -303,7 +306,6 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); * @param map The (const celix_string_hash_map_t*) map to iterate over. * @param iterName A iterName which will be of type celix_string_hash_map_iterator_t to hold the iterator. */ -//TODO test if the macro can be used nested #define CELIX_STRING_HASH_MAP_ITERATE(map, iterName) \ for (celix_string_hash_map_iterator_t iterName = celix_stringHashMap_begin(map); !celix_stringHashMapIterator_isEnd(&(iterName)); celix_stringHashMapIterator_next(&(iterName))) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index af8b892f1..9185fa64b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -387,6 +387,9 @@ static void celix_properties_createAndSetEntry( const double* doubleValue, const bool* boolValue, const celix_version_t* versionValue) { + if (properties == NULL) { + return; + } celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, boolValue, versionValue); if (entry != NULL) { @@ -696,12 +699,8 @@ const celix_version_t* celix_properties_getAsVersion( celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { return entry->typed.versionValue; - } else if (entry != NULL) { - //NOTE not converting to version, due to ownership - //TODO improve? - return NULL; } - return NULL; + return defaultValue; } void celix_properties_setVersion(celix_properties_t *props, const char *key, const celix_version_t* version) { @@ -767,7 +766,11 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro celix_properties_iterator_t iter; iter.index = 0; - memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); + if (!celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { + memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); + } else { + memset(&iter.entry, 0, sizeof(iter.entry)); + } memset(&iter._data, 0, sizeof(iter._data)); memcpy(iter._data, &internalIter, sizeof(internalIter)); From 2c5194ff1b254d88c8a112c28307233a044ca970 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Wed, 4 Jan 2023 20:21:32 +0100 Subject: [PATCH 05/63] Add unit test for celix types properties and fix some mem issues --- libs/utils/gtest/src/PropertiesTestSuite.cc | 23 +- libs/utils/gtest/src/VersionTestSuite.cc | 46 ++-- libs/utils/include/celix/Properties.h | 44 +-- libs/utils/include/celix_properties.h | 26 +- libs/utils/include/celix_utils.h | 16 +- libs/utils/include/celix_version.h | 8 +- libs/utils/src/properties.c | 281 ++++++++++---------- libs/utils/src/utils.c | 19 +- libs/utils/src/version.c | 9 +- 9 files changed, 273 insertions(+), 199 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 1528ddb27..f0537f752 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -284,6 +284,7 @@ TEST_F(PropertiesTestSuite, getType) { EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "version")); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, celix_properties_getType(props, "missing")); + celix_version_destroy(version); celix_properties_destroy(props); } @@ -326,6 +327,7 @@ TEST_F(PropertiesTestSuite, getEntry) { entry = celix_properties_getEntry(props, "key6"); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, entry.valueType); + celix_version_destroy(version); celix_properties_destroy(props); } @@ -403,13 +405,14 @@ TEST_F(PropertiesTestSuite, iterateOverProperties) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, getAsVersion) { +TEST_F(PropertiesTestSuite, getVersion) { auto* properties = celix_properties_create(); + auto* emptyVersion = celix_version_createEmptyVersion(); // Test getting a version property auto* expected = celix_version_createVersion(1, 2, 3, "test"); celix_properties_setVersion(properties, "key", expected); - const auto* actual = celix_properties_getAsVersion(properties, "key", nullptr); + const auto* actual = celix_properties_getVersion(properties, "key", nullptr); EXPECT_EQ(celix_version_getMajor(expected), celix_version_getMajor(actual)); EXPECT_EQ(celix_version_getMinor(expected), celix_version_getMinor(actual)); EXPECT_EQ(celix_version_getMicro(expected), celix_version_getMicro(actual)); @@ -417,16 +420,22 @@ TEST_F(PropertiesTestSuite, getAsVersion) { // Test getting a non-version property celix_properties_set(properties, "key2", "value"); - auto* emptyVersion = celix_version_createEmptyVersion(); - actual = celix_properties_getAsVersion(properties, "key2", emptyVersion); + actual = celix_properties_getVersion(properties, "key2", emptyVersion); EXPECT_EQ(celix_version_getMajor(actual), 0); EXPECT_EQ(celix_version_getMinor(actual), 0); EXPECT_EQ(celix_version_getMicro(actual), 0); EXPECT_STREQ(celix_version_getQualifier(actual), ""); + EXPECT_EQ(celix_properties_getVersion(properties, "non-existent", nullptr), nullptr); + celix_version_destroy(expected); - EXPECT_EQ(celix_properties_getAsVersion(properties, "non-existent", nullptr), nullptr); + // Test setting without copy + celix_properties_setVersionWithoutCopy(properties, "key3", celix_version_createVersion(3,3,3,"")); + actual = celix_properties_getVersion(properties, "key3", emptyVersion); + EXPECT_EQ(celix_version_getMajor(actual), 3); + EXPECT_EQ(celix_version_getMinor(actual), 3); + EXPECT_EQ(celix_version_getMicro(actual), 3); + EXPECT_STREQ(celix_version_getQualifier(actual), ""); - celix_version_destroy(expected); celix_version_destroy(emptyVersion); celix_properties_destroy(properties); -} \ No newline at end of file +} diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc index a0874ecf7..90e5e9f1c 100644 --- a/libs/utils/gtest/src/VersionTestSuite.cc +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -68,23 +68,25 @@ TEST_F(VersionTestSuite, create) { free(str); } -TEST_F(VersionTestSuite, clone) { - celix_version_t* version = nullptr; - celix_version_t* clone = nullptr; - char * str; - - str = celix_utils_strdup("abc"); - EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); - EXPECT_EQ(CELIX_SUCCESS, version_clone(version, &clone)); - EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, clone->major); - EXPECT_EQ(2, clone->minor); - EXPECT_EQ(3, clone->micro); - EXPECT_STREQ("abc", clone->qualifier); +TEST_F(VersionTestSuite, copy) { + auto* version = celix_version_create(1, 2, 3, "abc"); + auto* copy = celix_version_copy(version); + EXPECT_NE(nullptr, version); + EXPECT_NE(nullptr, copy); + EXPECT_EQ(1, celix_version_getMajor(copy)); + EXPECT_EQ(2, celix_version_getMinor(copy)); + EXPECT_EQ(3, celix_version_getMicro(copy)); + EXPECT_STREQ("abc", celix_version_getQualifier(copy)); + celix_version_destroy(copy); + celix_version_destroy(version); - version_destroy(clone); - version_destroy(version); - free(str); + copy = celix_version_copy(nullptr); //returns "empty" version + EXPECT_NE(nullptr, copy); + EXPECT_EQ(0, celix_version_getMajor(copy)); + EXPECT_EQ(0, celix_version_getMinor(copy)); + EXPECT_EQ(0, celix_version_getMicro(copy)); + EXPECT_STREQ("", celix_version_getQualifier(copy)); + celix_version_destroy(copy); } TEST_F(VersionTestSuite, createFromString) { @@ -277,8 +279,8 @@ TEST_F(VersionTestSuite, compare) { } TEST_F(VersionTestSuite, celix_version_compareToMajorMinor) { - celix_version_t *version1 = celix_version_createVersion(2, 2, 0, nullptr); - celix_version_t *version2 = celix_version_createVersion(2, 2, 4, "qualifier"); + auto* version1 = celix_version_create(2, 2, 0, nullptr); + auto* version2 = celix_version_create(2, 2, 4, "qualifier"); EXPECT_EQ(0, celix_version_compareToMajorMinor(version1, 2, 2)); EXPECT_EQ(0, celix_version_compareToMajorMinor(version2, 2, 2)); @@ -367,8 +369,8 @@ TEST_F(VersionTestSuite,semanticCompatibility) { TEST_F(VersionTestSuite, compareEmptyAndNullQualifier) { //nullptr or "" qualifier should be the same - auto* v1 = celix_version_createVersion(0, 0, 0, nullptr); - auto* v2 = celix_version_createVersion(0, 0, 0, ""); + auto* v1 = celix_version_create(0, 0, 0, nullptr); + auto* v2 = celix_version_create(0, 0, 0, ""); EXPECT_EQ(0, celix_version_compareTo(v1, v1)); EXPECT_EQ(0, celix_version_compareTo(v1, v2)); EXPECT_EQ(0, celix_version_compareTo(v2, v2)); @@ -379,7 +381,7 @@ TEST_F(VersionTestSuite, compareEmptyAndNullQualifier) { TEST_F(VersionTestSuite, fillString) { // Create a version object - celix_version_t* version = celix_version_createVersion(1, 2, 3, "alpha"); + auto* version = celix_version_create(1, 2, 3, "alpha"); // Test with buffer large enough to hold the formatted string char buffer[32]; @@ -392,4 +394,4 @@ TEST_F(VersionTestSuite, fillString) { EXPECT_FALSE(success); celix_version_destroy(version); -} \ No newline at end of file +} diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 111c7fe47..682fe490c 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -36,6 +36,7 @@ namespace celix { public: explicit PropertiesIterator(const celix_properties_t* props) { iter = celix_properties_begin(props); + setFields(); } PropertiesIterator& operator++() { @@ -60,27 +61,27 @@ namespace celix { void next() { celix_propertiesIterator_next(&iter); - if (celix_propertiesIterator_isEnd(&iter)) { - moveToEnd(); - } else { - first = iter.entry.key; - second = iter.entry.value; - end = false; - } + setFields(); } - //TODO try to remove moveToEnd void moveToEnd() { first = {}; second = {}; end = true; } - //TODO for C++17 try to update first and second to stringview std::string first{}; std::string second{}; - //TODO iter? private: + void setFields() { + if (celix_propertiesIterator_isEnd(&iter)) { + moveToEnd(); + } else { + first = iter.entry.key; + second = iter.entry.value; + } + } + celix_properties_iterator_t iter{.index = -1, .entry = {}, ._data = {}}; bool end{false}; }; @@ -130,8 +131,6 @@ namespace celix { } #endif - //TODO operator= with long, double, boolean and version - [[nodiscard]] const char* getValue() const { if (charKey == nullptr) { return celix_properties_get(props.get(), stringKey.c_str(), nullptr); @@ -140,6 +139,8 @@ namespace celix { } } + //TODO get typed value + operator std::string() const { auto *cstr = getValue(); return cstr == nullptr ? std::string{} : std::string{cstr}; @@ -290,6 +291,10 @@ namespace celix { return celix_properties_getAsBool(cProps.get(), key.data(), defaultValue); } + //TODO getType + + //TODO getAsVersion + /** * @brief Sets a T&& property. Will use (std::) to_string to convert the value to string. */ @@ -367,6 +372,8 @@ namespace celix { using namespace std; celix_properties_set(cProps.get(), key.c_str(), to_string(value).c_str()); } + + //TODO set long, double, boolean and version #endif /** @@ -382,7 +389,7 @@ namespace celix { [[nodiscard]] std::map convertToMap() const { std::map result{}; for (const auto& pair : *this) { - result[pair.first] = pair.second; + result[std::string{pair.first}] = pair.second; } return result; } @@ -393,7 +400,7 @@ namespace celix { [[nodiscard]] std::unordered_map convertToUnorderedMap() const { std::unordered_map result{}; for (const auto& pair : *this) { - result[pair.first] = pair.second; + result[std::string{pair.first}] = pair.second; } return result; } @@ -411,8 +418,13 @@ namespace celix { #endif - //TODO save - //TODO load + //TODO test + void store(const std::string& file, const std::string& header = {}) const { + celix_properties_store(cProps.get(), file.c_str(), header.empty() ? nullptr : header.c_str()); + } + + //TODO laod + private: explicit Properties(celix_properties_t* props) : cProps{props, [](celix_properties_t*) { /*nop*/ }} {} diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 45846506c..e484a436a 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -284,14 +284,30 @@ void celix_properties_setDouble(celix_properties_t* properties, const char* key, double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); /** - * @brief Sets the value of a property as a Celix version string. + * @brief Sets the value of a property as a Celix version. * - * @param properties The property set to modify. - * @param key The key of the property to set. - * @param version The value to set. + * This function will make a copy of the provided celix_version_t object and store it in the property set. + * + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] version The value to set. The function will make a copy of this object and store it in the property set. */ void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); +/** + * @brief Sets the value of a property as a Celix version. + * + * This function will store a reference to the provided celix_version_t object in the property set and takes + * ownership of the provided version. + * + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] version The value to set. The function will store a reference to this object in the property set and + * takes ownership of the provided version. + */ +void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); + + /** * @brief Gets the value of a property as a Celix version. * @@ -303,7 +319,7 @@ void celix_properties_setVersion(celix_properties_t* properties, const char* key * @return The value of the property if it is a Celix version, or the default value if the property is not set or the * value is not a Celix version. */ -const celix_version_t* celix_properties_getAsVersion( +const celix_version_t* celix_properties_getVersion( const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue); diff --git a/libs/utils/include/celix_utils.h b/libs/utils/include/celix_utils.h index e0db27312..791d8f89b 100644 --- a/libs/utils/include/celix_utils.h +++ b/libs/utils/include/celix_utils.h @@ -54,11 +54,23 @@ bool celix_utils_stringEquals(const char* a, const char* b); /** * @brief Returns a trimmed string. * - * The trim will remove eny leading and trailing whitespaces (' ', '\t', etc based on `isspace`)/ - * Caller is owner of the returned string. + * This function will remove any leading and trailing whitespaces (' ', '\t', etc based on isspace) from the + * input string. + * + * @param[in] string The input string to be trimmed. + * @return A trimmed version of the input string. The caller is responsible for freeing the memory of this string. */ char* celix_utils_trim(const char* string); +/** + * @brief Trims a string in place. + * + * The trim will remove any leading and trailing whitespaces (' ', '\t', etc based on isspace) from the input string. + * The input string is modified in place. + * @param[in,out] string The string to trim. + */ +void celix_utils_trimInPlace(char* string); + /** * @brief Check if a string is NULL or empty "". */ diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index be03aeef1..f4cea3907 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -42,7 +42,13 @@ typedef struct celix_version celix_version_t; * the empty string. * @return The created version or NULL if the input was incorrect */ -celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier); +celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier); + +/** + * @brief Same as celix_version_create, but deprecated + */ +celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier) + __attribute__((deprecated(("celix_version_createVersion is deprecated use celix_version_create instead")))); void celix_version_destroy(celix_version_t* version); diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 9185fa64b..7be3e7135 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -137,106 +137,6 @@ static void updateBuffers(char **key, char ** value, char **output, int outputPo } } -static void parseLine(const char* line, celix_properties_t *props) { - int linePos = 0; - bool precedingCharIsBackslash = false; - bool isComment = false; - int outputPos = 0; - char *output = NULL; - int key_len = MALLOC_BLOCK_SIZE; - int value_len = MALLOC_BLOCK_SIZE; - linePos = 0; - precedingCharIsBackslash = false; - isComment = false; - output = NULL; - outputPos = 0; - - //Ignore empty lines - if (line[0] == '\n' && line[1] == '\0') { - return; - } - - char *key = calloc(1, key_len); - char *value = calloc(1, value_len); - key[0] = '\0'; - value[0] = '\0'; - - while (line[linePos] != '\0') { - if (line[linePos] == ' ' || line[linePos] == '\t') { - if (output == NULL) { - //ignore - linePos += 1; - continue; - } - } - else { - if (output == NULL) { - output = key; - } - } - if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') { - if (precedingCharIsBackslash) { - //escaped special character - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - precedingCharIsBackslash = false; - } - else { - if (line[linePos] == '#' || line[linePos] == '!') { - if (outputPos == 0) { - isComment = true; - break; - } - else { - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - } - else { // = or : - if (output == value) { //already have a seperator - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - else { - output[outputPos++] = '\0'; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - output = value; - outputPos = 0; - } - } - } - } - else if (line[linePos] == '\\') { - if (precedingCharIsBackslash) { //double backslash -> backslash - output[outputPos++] = '\\'; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - precedingCharIsBackslash = true; - } - else { //normal character - precedingCharIsBackslash = false; - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - linePos += 1; - } - if (output != NULL) { - output[outputPos] = '\0'; - } - - if (!isComment) { - //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); - celix_properties_set(props, utils_stringTrim(key), utils_stringTrim(value)); - } - if(key) { - free(key); - } - if(value) { - free(value); - } - -} - /** * Create a new string from the provided str by either using strup or storing the string the short properties * optimization string buffer. @@ -266,7 +166,7 @@ static celix_status_t celix_properties_fillEntry( const long* longValue, const double* doubleValue, const bool* boolValue, - const celix_version_t* versionValue) { + celix_version_t* versionValue) { char convertedValueBuffer[32]; entry->key = celix_properties_createString(properties, key); if (strValue != NULL) { @@ -302,8 +202,7 @@ static celix_status_t celix_properties_fillEntry( } else /*versionValue*/ { assert(versionValue != NULL); entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; - entry->typed.versionValue = celix_version_copy(versionValue); - + entry->typed.versionValue = versionValue; bool written = celix_version_fillString(versionValue, convertedValueBuffer, sizeof(convertedValueBuffer)); if (written) { entry->value = celix_properties_createString(properties, convertedValueBuffer); @@ -360,7 +259,7 @@ static celix_properties_entry_t* celix_properties_createEntry( const long* longValue, const double* doubleValue, const bool* boolValue, - const celix_version_t* versionValue) { + celix_version_t* versionValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { return NULL; @@ -386,7 +285,7 @@ static void celix_properties_createAndSetEntry( const long* longValue, const double* doubleValue, const bool* boolValue, - const celix_version_t* versionValue) { + celix_version_t* versionValue) { if (properties == NULL) { return; } @@ -415,6 +314,9 @@ static void celix_properties_removeEntryCallback(void* handle, const char* key _ celix_properties_entry_t* entry = val.ptrValue; celix_properties_freeString(properties, (char*)entry->key); celix_properties_freeString(properties, (char*)entry->value); + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy(entry->typed.versionValue); + } if (entry >= properties->entriesBuffer && entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { //entry is part of the properties entries buffer -> nop. @@ -423,7 +325,6 @@ static void celix_properties_removeEntryCallback(void* handle, const char* key _ } } - celix_properties_t* celix_properties_create(void) { celix_properties_t* props = malloc(sizeof(*props)); if (props != NULL) { @@ -456,38 +357,143 @@ celix_properties_t* celix_properties_load(const char *filename) { return props; } -celix_properties_t* celix_properties_loadWithStream(FILE *file) { - celix_properties_t *props = NULL; - - if (file != NULL ) { - char *saveptr; - char *filebuffer = NULL; - char *line = NULL; - size_t file_size = 0; - - props = celix_properties_create(); - fseek(file, 0, SEEK_END); - file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - if (file_size > 0) { - filebuffer = calloc(file_size + 1, sizeof(char)); - if (filebuffer) { - size_t rs = fread(filebuffer, sizeof(char), file_size, file); - if (rs != file_size) { - fprintf(stderr,"fread read only %lu bytes out of %lu\n", (long unsigned int) rs, (long unsigned int) file_size); +static void parseLine(const char* line, celix_properties_t *props) { + int linePos = 0; + bool precedingCharIsBackslash = false; + bool isComment = false; + int outputPos = 0; + char *output = NULL; + int key_len = MALLOC_BLOCK_SIZE; + int value_len = MALLOC_BLOCK_SIZE; + linePos = 0; + precedingCharIsBackslash = false; + isComment = false; + output = NULL; + outputPos = 0; + + //Ignore empty lines + if (line[0] == '\n' && line[1] == '\0') { + return; + } + + char *key = calloc(1, key_len); + char *value = calloc(1, value_len); + key[0] = '\0'; + value[0] = '\0'; + + while (line[linePos] != '\0') { + if (line[linePos] == ' ' || line[linePos] == '\t') { + if (output == NULL) { + //ignore + linePos += 1; + continue; + } + } + else { + if (output == NULL) { + output = key; + } + } + if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') { + if (precedingCharIsBackslash) { + //escaped special character + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + precedingCharIsBackslash = false; + } + else { + if (line[linePos] == '#' || line[linePos] == '!') { + if (outputPos == 0) { + isComment = true; + break; + } + else { + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } } - filebuffer[file_size]='\0'; - line = strtok_r(filebuffer, "\n", &saveptr); - while (line != NULL) { - parseLine(line, props); - line = strtok_r(NULL, "\n", &saveptr); + else { // = or : + if (output == value) { //already have a seperator + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + else { + output[outputPos++] = '\0'; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + output = value; + outputPos = 0; + } } - free(filebuffer); } } + else if (line[linePos] == '\\') { + if (precedingCharIsBackslash) { //double backslash -> backslash + output[outputPos++] = '\\'; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + precedingCharIsBackslash = true; + } + else { //normal character + precedingCharIsBackslash = false; + output[outputPos++] = line[linePos]; + updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); + } + linePos += 1; + } + if (output != NULL) { + output[outputPos] = '\0'; } + if (!isComment) { + //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); + celix_utils_trimInPlace(key); + celix_utils_trimInPlace(value); + celix_properties_setWithoutCopy(props, key, value); + } else { + free(key); + free(value); + } + + +} + +celix_properties_t* celix_properties_loadWithStream(FILE *file) { + if (file == NULL) { + return NULL; + } + + celix_properties_t *props = celix_properties_create(); + if (props == NULL) { + return NULL; + } + + fseek(file, 0, SEEK_END); + size_t fileSize = ftell(file); + fseek(file, 0, SEEK_SET); + if (fileSize == 0) { + return props; + } + + char* fileBuffer = malloc(fileSize + 1); + if (fileBuffer == NULL) { + celix_properties_destroy(props); + return NULL; + } + + size_t rs = fread(fileBuffer, sizeof(char), fileSize, file); + if (rs < fileSize) { + fprintf(stderr,"fread read only %zu bytes out of %zu\n", rs, fileSize); + } + fileBuffer[fileSize]='\0'; //ensure a '\0' at the end of the fileBuffer + + char* savePtr = NULL; + char* line = strtok_r(fileBuffer, "\n", &savePtr); + while (line != NULL) { + parseLine(line, props); + line = strtok_r(NULL, "\n", &savePtr); + } + free(fileBuffer); + return props; } @@ -692,7 +698,7 @@ void celix_properties_setBool(celix_properties_t *props, const char *key, bool v celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); } -const celix_version_t* celix_properties_getAsVersion( +const celix_version_t* celix_properties_getVersion( const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue) { @@ -703,10 +709,13 @@ const celix_version_t* celix_properties_getAsVersion( return defaultValue; } -void celix_properties_setVersion(celix_properties_t *props, const char *key, const celix_version_t* version) { - celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, NULL, version); +void celix_properties_setVersion(celix_properties_t *properties, const char *key, const celix_version_t* version) { + celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); } +void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) { + celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version); +} int celix_properties_size(const celix_properties_t *properties) { return (int)celix_stringHashMap_size(properties->map); @@ -748,7 +757,7 @@ const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) iter->index = (int)internalIter.mapIter.index; celix_properties_entry_t* entry = internalIter.mapIter.value.ptrValue; if (entry != NULL) { - memcpy(&iter->entry, iter, sizeof(iter->entry)); + memcpy(&iter->entry, entry, sizeof(iter->entry)); } else { memset(&iter->entry, 0, sizeof(iter->entry)); } diff --git a/libs/utils/src/utils.c b/libs/utils/src/utils.c index 7bbe2f4c6..6ff882a87 100644 --- a/libs/utils/src/utils.c +++ b/libs/utils/src/utils.c @@ -82,12 +82,12 @@ char * string_ndup(const char *s, size_t n) { return ret; } -static char* celix_utilsTrimInternal(char *string) { +void celix_utils_trimInPlace(char* string) { if (string == NULL) { - return NULL; + return; } - char* begin = string; //save begin to correctly free in the end. + char* begin = string; //save begin to check in the end. char *end; // Trim leading space @@ -103,7 +103,7 @@ static char* celix_utilsTrimInternal(char *string) { } if (string != begin) { - //beginning whitespaces -> move char in copy to to begin string + //beginning whitespaces -> move chars to the beginning of string //This to ensure free still works on the same pointer. char* nstring = begin; while(*string != '\0') { @@ -111,16 +111,17 @@ static char* celix_utilsTrimInternal(char *string) { } (*nstring) = '\0'; } - - return begin; } char* celix_utils_trim(const char* string) { - return celix_utilsTrimInternal(celix_utils_strdup(string)); + char* result = celix_utils_strdup(string); + celix_utils_trimInPlace(result); + return result; } char* utils_stringTrim(char* string) { - return celix_utilsTrimInternal(string); + celix_utils_trimInPlace(string); + return string; } bool utils_isStringEmptyOrNull(const char * const str) { @@ -264,4 +265,4 @@ void celix_utils_extractLocalNameAndNamespaceFromFullyQualifiedName(const char * } else { *outNamespace = namespace; } -} \ No newline at end of file +} diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index f83fe6555..3d0ee1ab7 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -96,6 +96,10 @@ celix_status_t version_isCompatible(version_pt user, version_pt provider, bool* } celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier) { + return celix_version_create(major, minor, micro, qualifier); +} + +celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier) { if (major < 0 || minor < 0 || micro < 0) { return NULL; } @@ -141,6 +145,9 @@ void celix_version_destroy(celix_version_t* version) { celix_version_t* celix_version_copy(const celix_version_t* version) { + if (version == NULL) { + return celix_version_createEmptyVersion(); + } return celix_version_createVersion(version->major, version->minor, version->micro, version->qualifier); } @@ -322,4 +329,4 @@ int celix_version_compareToMajorMinor(const celix_version_t* version, int majorV result = version->minor - minorVersionPart; } return result; -} \ No newline at end of file +} From 740bbbfbd41c713c018ca50bc1f1df3e564fbb4c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 5 Jan 2023 22:31:56 +0100 Subject: [PATCH 06/63] Add properties type support to C++ properties --- .../utils/gtest/src/CxxPropertiesTestSuite.cc | 29 ++ libs/utils/gtest/src/HashMapTestSuite.cc | 28 ++ libs/utils/gtest/src/PropertiesTestSuite.cc | 22 ++ libs/utils/include/celix/Properties.h | 277 +++++++++++++++--- libs/utils/include/celix/Version.h | 121 ++++++++ libs/utils/include/celix_long_hash_map.h | 21 +- libs/utils/include/celix_properties.h | 143 +++++---- libs/utils/include/celix_string_hash_map.h | 26 +- libs/utils/src/celix_hash_map.c | 18 +- libs/utils/src/properties.c | 31 +- libs/utils/src/version.c | 13 +- 11 files changed, 603 insertions(+), 126 deletions(-) create mode 100644 libs/utils/include/celix/Version.h diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index d5acac775..bc60adbc2 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -105,6 +105,35 @@ TEST_F(CxxPropertiesTestSuite, testWrap) { celix_properties_destroy(props); } +TEST_F(CxxPropertiesTestSuite, getType) { + celix::Properties props{}; + + props.set("bool", true); + props.set("long1", 1l); + props.set("long2", (int)1); //should lead to long; + props.set("long3", (unsigned int)1); //should lead to long; + props.set("long4", (short)1); //should lead to long; + props.set("long5", (unsigned short)1); //should lead to long; + props.set("long6", (char)1); //should lead to long; + props.set("long7", (unsigned char)1); //should lead to long; + props.set("double1", 1.0); + props.set("double2", 1.0f); //set float should lead to double + //TODO version + + EXPECT_EQ(props.getType("bool"), celix::Properties::ValueType::Bool); + EXPECT_EQ(props.getType("long1"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long2"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long3"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long4"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long5"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long6"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("long7"), celix::Properties::ValueType::Long); + EXPECT_EQ(props.getType("double1"), celix::Properties::ValueType::Double); + EXPECT_EQ(props.getType("double2"), celix::Properties::ValueType::Double); + +} + + #if __cplusplus >= 201703L //C++17 or higher TEST_F(CxxPropertiesTestSuite, testStringView) { constexpr std::string_view stringViewKey = "KEY1"; diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index fa0e7f622..85a4944f0 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -546,3 +546,31 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); celix_longHashMap_destroy(lMap); } + +TEST_F(HashMapTestSuite, IterateEndTest) { + auto* sMap1 = createStringHashMap(0); + auto* sMap2 = createStringHashMap(6); + auto* lMap1 = createLongHashMap(0); + auto* lMap2 = createLongHashMap(6); + + auto sIter1 = celix_stringHashMap_end(sMap1); + auto sIter2 = celix_stringHashMap_end(sMap2); + auto lIter1 = celix_longHashMap_end(lMap1); + auto lIter2 = celix_longHashMap_end(lMap2); + + EXPECT_EQ(sIter1.index, 0); + EXPECT_EQ(sIter2.index, 6); + EXPECT_EQ(lIter1.index, 0); + EXPECT_EQ(lIter2.index, 6); + EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter1)); + EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter2)); + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1)); + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter2)); + + //TODO loop and test with index. + + celix_stringHashMap_destroy(sMap1); + celix_stringHashMap_destroy(sMap2); + celix_longHashMap_destroy(lMap1); + celix_longHashMap_destroy(lMap2); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index f0537f752..b9b6eb95d 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -439,3 +439,25 @@ TEST_F(PropertiesTestSuite, getVersion) { celix_version_destroy(emptyVersion); celix_properties_destroy(properties); } + +TEST_F(PropertiesTestSuite, TestEndOfProperties) { + auto* props = celix_properties_create(); + celix_properties_set(props, "key1", "value1"); + celix_properties_set(props, "key2", "value2"); + + celix_properties_iterator_t endIter = celix_properties_end(props); + EXPECT_EQ(endIter.index, 2); + EXPECT_TRUE(celix_propertiesIterator_isEnd(&endIter)); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, TestEndOfEmptyProperties) { + auto* props = celix_properties_create(); + + celix_properties_iterator_t endIter = celix_properties_end(props); + EXPECT_EQ(endIter.index, 0); + EXPECT_TRUE(celix_propertiesIterator_isEnd(&endIter)); + + celix_properties_destroy(props); +} diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 682fe490c..c75119a3c 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -26,6 +26,7 @@ #include "celix_properties.h" #include "celix_utils.h" +#include "celix/Version.h" namespace celix { @@ -39,6 +40,11 @@ namespace celix { setFields(); } + explicit PropertiesIterator(celix_properties_iterator_t _iter) { + iter = std::move(_iter); + setFields(); + } + PropertiesIterator& operator++() { next(); return *this; @@ -64,18 +70,13 @@ namespace celix { setFields(); } - void moveToEnd() { - first = {}; - second = {}; - end = true; - } - std::string first{}; std::string second{}; private: void setFields() { if (celix_propertiesIterator_isEnd(&iter)) { - moveToEnd(); + first = {}; + second = {}; } else { first = iter.entry.key; second = iter.entry.value; @@ -97,6 +98,19 @@ namespace celix { public: using const_iterator = PropertiesIterator; + /** + * @brief Enum representing the possible types of a property value. + */ + enum class ValueType { + Unset, /**< Property value is not set. */ + String, /**< Property value is a string. */ + Long, /**< Property value is a long integer. */ + Double, /**< Property value is a double. */ + Bool, /**< Property value is a boolean. */ + Version /**< Property value is a Celix version. */ + }; + + class ValueRef { public: #if __cplusplus >= 201703L //C++17 or higher @@ -139,8 +153,6 @@ namespace celix { } } - //TODO get typed value - operator std::string() const { auto *cstr = getValue(); return cstr == nullptr ? std::string{} : std::string{cstr}; @@ -240,9 +252,7 @@ namespace celix { * @brief end iterator */ [[nodiscard]] const_iterator end() const noexcept { - auto iter = PropertiesIterator{cProps.get()}; - iter.moveToEnd(); - return iter; + return PropertiesIterator{celix_properties_end(cProps.get())}; } /** @@ -256,9 +266,7 @@ namespace celix { * @brief constant end iterator */ [[nodiscard]] const_iterator cend() const noexcept { - auto iter = PropertiesIterator{cProps.get()}; - iter.moveToEnd(); - return iter; + return PropertiesIterator{celix_properties_end(cProps.get())}; } #if __cplusplus >= 201703L //C++17 or higher @@ -271,32 +279,85 @@ namespace celix { } /** - * @brief Get the value as long for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a long. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a long. + * @return The long value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] long getAsLong(std::string_view key, long defaultValue) const { return celix_properties_getAsLong(cProps.get(), key.data(), defaultValue); } /** - * @brief Get the value as double for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a double. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a double. + * @return The double value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] double getAsDouble(std::string_view key, double defaultValue) const { return celix_properties_getAsDouble(cProps.get(), key.data(), defaultValue); } /** - * @brief Get the value as bool for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a boolean. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a boolean. + * @return The boolean value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] bool getAsBool(std::string_view key, bool defaultValue) const { return celix_properties_getAsBool(cProps.get(), key.data(), defaultValue); } - //TODO getType + /** + * @brief Get the value of the property with key as a Celix version. + * + * Note that this function does not automatically convert a string property value to a Celix version. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value is not a Celix + * version. + * @return The value of the property if it is a Celix version, or the default value if the property is not set + * or the value is not a Celix version. + */ + //TODO test + [[nodiscard]] celix::Version getVersion(std::string_view key, celix::Version defaultValue = {}) { + auto* cVersion = celix_properties_getVersion(cProps.get(), key.data(), nullptr); + if (cVersion) { + return celix::Version{ + celix_version_getMajor(cVersion), + celix_version_getMinor(cVersion), + celix_version_getMicro(cVersion), + celix_version_getQualifier(cVersion)}; + } + return defaultValue; + } - //TODO getAsVersion + /** + * @brief Get the type of the property with key. + * + * @param[in] key The key of the property to get the type for. + * @return The type of the property with the given key, or ValueType::Unset if the property + * does not exist. + */ + //TODO test + [[nodiscard]] ValueType getType(std::string_view key) { + return getAndConvertType(cProps, key.data()); + } /** - * @brief Sets a T&& property. Will use (std::) to_string to convert the value to string. + * @brief Set the value of a property. + * + * @tparam T The type of the value to set. This can be one of: bool, std::string_view, a type that is + * convertible to std::string_view, bool, long, double, celix::Version, or celix_version_t*. + * @param[im] key The key of the property to set. + * @param[in] value The value to set. If the type of the value is not one of the above types, it will be + * converted to a string using std::to_string before being set. */ template void set(std::string_view key, T&& value) { @@ -307,8 +368,17 @@ namespace celix { } else if constexpr (std::is_convertible_v) { std::string_view view{value}; celix_properties_set(cProps.get(), key.data(), view.data()); + } else if constexpr (std::is_same_v, bool>) { + celix_properties_setBool(cProps.get(), key.data(), value); + } else if constexpr (std::is_convertible_v, long>) { + celix_properties_setLong(cProps.get(), key.data(), value); + } else if constexpr (std::is_convertible_v, double>) { + celix_properties_setDouble(cProps.get(), key.data(), value); + } else if constexpr (std::is_same_v, celix::Version>) { + celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); + } else if constexpr (std::is_same_v) { + celix_properties_setVersion(cProps.get(), key.data(), value); } else { - //TODO spit up to use setLong, setBool, setDouble and setVersion using namespace std; celix_properties_set(cProps.get(), key.data(), to_string(value).c_str()); } @@ -323,68 +393,181 @@ namespace celix { } /** - * @brief Get the value as long for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a long. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a long. + * @return The long value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] long getAsLong(const std::string& key, long defaultValue) const { return celix_properties_getAsLong(cProps.get(), key.c_str(), defaultValue); } /** - * @brief Get the value as double for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a double. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a double. + * @return The double value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] double getAsDouble(const std::string &key, double defaultValue) const { return celix_properties_getAsDouble(cProps.get(), key.c_str(), defaultValue); } /** - * @brief Get the value as bool for a property key or return the defaultValue if the key does not exists. + * @brief Get the value of the property with key as a boolean. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a boolean. + * @return The boolean value of the property if it exists and can be converted, or the default value otherwise. */ [[nodiscard]] bool getAsBool(const std::string &key, bool defaultValue) const { return celix_properties_getAsBool(cProps.get(), key.c_str(), defaultValue); } + + /** + * @brief Get the value of the property with key as a Celix version. + * + * Note that this function does not automatically convert a string property value to a Celix version. + * + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value is not a Celix + * version. + * @return The value of the property if it is a Celix version, or the default value if the property is not set + * or the value is not a Celix version. + */ + [[nodiscard]] celix::Version getVersion(const std::string& key, celix::Version defaultValue = {}) { + auto* cVersion = celix_properties_getVersion(cProps.get(), key.data(), nullptr); + if (cVersion) { + return celix::Version{ + celix_version_getMajor(cVersion), + celix_version_getMinor(cVersion), + celix_version_getMicro(cVersion), + celix_version_getQualifier(cVersion)}; + } + return defaultValue; + } /** - * @brief Sets a property + * @brief Get the type of the property with key. + * + * @param[in] key The key of the property to get the type for. + * @return The type of the property with the given key, or ValueType::Unset if the property + * does not exist. + */ + [[nodiscard]] ValueType getType(const std::string& key) { + return getAndConvertType(cProps, key.data()); + } + + /** + * @brief Set the value of a property. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set the property to. */ void set(const std::string& key, const std::string& value) { - celix_properties_set(cProps.get(), key.c_str(), value.c_str()); + celix_properties_set(cProps.get(), key.data(), value.c_str()); } /** - * @brief Sets a bool property + * @brief Set the value of a property to a boolean. + * + * @param[in] key The key of the property to set. + * @param[in] value The boolean value to set the property to. */ void set(const std::string& key, bool value) { - celix_properties_setBool(cProps.get(), key.c_str(), value); + celix_properties_setBool(cProps.get(), key.data(), value); } /** - * @brief Sets a const char* property + * @brief Set the value of a property. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set the property to. */ void set(const std::string& key, const char* value) { - celix_properties_set(cProps.get(), key.c_str(), value); + celix_properties_set(cProps.get(), key.data(), value); } /** - * @brief Sets a T property. Will use (std::) to_string to convert the value to string. + * @brief Sets a property with a value of type T. + * + * This function will use the std::to_string function to convert the value of type T to a string, + * which will be used as the value for the property. + * + * @tparam T The type of the value to set. + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. */ template void set(const std::string& key, T&& value) { using namespace std; - celix_properties_set(cProps.get(), key.c_str(), to_string(value).c_str()); + celix_properties_set(cProps.get(), key.data()(), to_string(value).c_str()); + } + + /** + * @brief Sets a bool property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, bool value) { + celix_properties_setBool(cProps.get(), key.data(), value); + } + + /** + * @brief Sets a long property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, long value) { + celix_properties_setLong(cProps.get(), key.data(), value); + } + + /** + * @brief Sets a double property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, double value) { + celix_properties_setDouble(cProps.get(), key.data(), value); + } + + /** + * @brief Sets a celix::Version property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, const celix::Version& value) { + celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); } - //TODO set long, double, boolean and version + /** + * @brief Sets a celix_version_t* property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, const celix_version_t* value) { + celix_properties_setVersion(cProps.get(), key.data(), value); + } #endif /** - * @brief Returns the nr of properties. + * @brief Returns the number of properties in the Properties object. */ [[nodiscard]] std::size_t size() const { return celix_properties_size(cProps.get()); } /** - * @brief Converts the properties a (new) std::string, std::string map. + * @brief Convert the properties a (new) std::string, std::string map. */ [[nodiscard]] std::map convertToMap() const { std::map result{}; @@ -395,7 +578,7 @@ namespace celix { } /** - * @brief Converts the properties a (new) std::string, std::string unordered map. + * @brief Convert the properties a (new) std::string, std::string unordered map. */ [[nodiscard]] std::unordered_map convertToUnorderedMap() const { std::unordered_map result{}; @@ -428,6 +611,26 @@ namespace celix { private: explicit Properties(celix_properties_t* props) : cProps{props, [](celix_properties_t*) { /*nop*/ }} {} + static celix::Properties::ValueType getAndConvertType( + const std::shared_ptr& cProperties, + const char* key) { + auto cType = celix_properties_getType(cProperties.get(), key); + switch (cType) { + case CELIX_PROPERTIES_VALUE_TYPE_STRING: + return ValueType::String; + case CELIX_PROPERTIES_VALUE_TYPE_LONG: + return ValueType::Long; + case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: + return ValueType::Double; + case CELIX_PROPERTIES_VALUE_TYPE_BOOL: + return ValueType::Bool; + case CELIX_PROPERTIES_VALUE_TYPE_VERSION: + return ValueType::Version; + default: /*unset*/ + return ValueType::Unset; + } + } + std::shared_ptr cProps; }; diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h new file mode 100644 index 000000000..ce70d8d4c --- /dev/null +++ b/libs/utils/include/celix/Version.h @@ -0,0 +1,121 @@ +/* + * 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. + */ + +#pragma once + +#include +#include + +#include "celix_version.h" + +namespace celix { + + //TODO CxxVersionTestSuite + //TODO doxygen + class Version { + public: + Version() : cVersion{createVersion(celix_version_createEmptyVersion())} {} +#if __cplusplus >= 201703L //C++17 or higher + explicit Version(int major, int minor, int micro, std::string_view qualifier = {}) : + cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))} {} +#else + explicit Version(int major, int minor, int micro, const& std::string qualifier = {}) : + cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))} {} +#endif + + + Version(Version&&) = default; + + Version& operator=(Version&&) = default; + + Version(const Version& rhs) : cVersion{createVersion(celix_version_copy(rhs.cVersion.get()))} {} + + Version& operator=(const Version& rhs) { + if (this != &rhs) { + cVersion = createVersion(rhs.cVersion.get()); + } + return *this; + } + + bool operator==(const Version& rhs) { + return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) == 0; + } + + bool operator<(const Version& rhs) { + return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) < 0; + } + + //TODO rest of the operators + + /** + * @brief Warps a C Celix Version to a C++ Celix Version, but takes no ownership. + * Dealloction is still the responsibility of the caller. + */ + static Version wrap(celix_version_t* v) { + return Version{v}; + } + + /** + * @brief Get the underlining C Celix Version object. + * + * @warning Try not the depend on the C API from a C++ bundle. If features are missing these should be added to + * the C++ API. + */ + [[nodiscard]] celix_version_t * getCVersion() const { + return cVersion.get(); + } + + [[nodiscard]] int getMajor() const { + return celix_version_getMajor(cVersion.get()); + } + + [[nodiscard]] int getMinor() const { + return celix_version_getMinor(cVersion.get()); + } + + [[nodiscard]] int getMicro() const { + return celix_version_getMicro(cVersion.get()); + } + + [[nodiscard]] std::string getQualifier() const { + return std::string{celix_version_getQualifier(cVersion.get())}; + } + + /** + * @brief Return whether the version is an empty version (0.0.0.""). + */ + [[nodiscard]] bool emptyVersion() const { + //TODO celix_version_isEmpty(cVersion.get()); + return false; + } + private: + static std::shared_ptr createVersion(celix_version_t* cVersion) { + return std::shared_ptr{cVersion, [](celix_version_t *v) { + celix_version_destroy(v); + }}; + } + + /** + * @brief Create a wrap around a C Celix version without taking ownership. + */ + explicit Version(celix_version_t* v) : cVersion{v, [](celix_version_t *){/*nop*/}} {} + + std::shared_ptr cVersion; + }; +} \ No newline at end of file diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 50bf810d3..40e23bffd 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -261,24 +261,33 @@ bool celix_longHashMap_remove(celix_long_hash_map_t* map, long key); void celix_longHashMap_clear(celix_long_hash_map_t* map); /** - * @brief Returns an iterator pointing to the first element in the map. + * @brief Get an iterator pointing to the first element in the map. * - * @param map The map to get the iterator for. + * @param[in] map The map to get the iterator for. * @return An iterator pointing to the first element in the map. */ celix_long_hash_map_iterator_t celix_longHashMap_begin(const celix_long_hash_map_t* map); /** - * @brief Check if the iterator is the end of the hash map. + * @brief Get an iterator pointing to the element following the last element in the map. * - * @note the end iterator should not be used to retrieve a key of value. + * @param[in] map The map to get the iterator for. + * @return An iterator pointing to the element following the last element in the map. + */ +celix_long_hash_map_iterator_t celix_longHashMap_end(const celix_long_hash_map_t* map); + +/** + * + * @brief Determine if the iterator points to the element following the last element in the map. * - * @return true if the iterator is the end. + * @param[in] iter The iterator to check. + * @return true if the iterator points to the element following the last element in the map, false otherwise. */ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter); /** - * @brief Moves the provided iterator to the next entry in the hash map. + * @brief Advance the iterator to the next element in the map. + * @param[in] iter The iterator to advance. */ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index e484a436a..7794661ec 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -103,6 +103,7 @@ typedef struct celix_properties_iterator { /** * @brief Creates a new empty property set. + * * @return A new empty property set. */ celix_properties_t* celix_properties_create(void); @@ -110,14 +111,14 @@ celix_properties_t* celix_properties_create(void); /** * @brief Destroys a property set, freeing all associated resources. * - * @param properties The property set to destroy. If properties is NULL, this function will do nothing. + * @param[in] properties The property set to destroy. If properties is NULL, this function will do nothing. */ void celix_properties_destroy(celix_properties_t* properties); /** * @brief Loads properties from a file. * - * @param filename The name of the file to load properties from. + * @param[in] filename The name of the file to load properties from. * @return A property set containing the properties from the file. * @retval NULL If an error occurred (e.g. file not found). */ @@ -127,7 +128,7 @@ celix_properties_t* celix_properties_load(const char *filename); /** * @brief Loads properties from a stream. * - * @param stream The stream to load properties from. + * @param[in,out] stream The stream to load properties from. * @return A property set containing the properties from the stream. * @retval NULL If an error occurred (e.g. invalid format). */ @@ -136,7 +137,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE *stream); /** * @brief Loads properties from a string. * - * @param input The string to load properties from. + * @param[in] input The string to load properties from. * @return A property set containing the properties from the string. * @retval NULL If an error occurred (e.g. invalid format). */ @@ -145,9 +146,9 @@ celix_properties_t* celix_properties_loadFromString(const char *input); /** * @brief Stores properties to a file. * - * @param properties The property set to store. - * @param file The name of the file to store the properties to. - * @param header An optional header to write to the file before the properties. + * @param[in] properties The property set to store. + * @param[in] file The name of the file to store the properties to. + * @param[in] header An optional header to write to the file before the properties. * @return CELIX_SUCCESS if the operation was successful, CELIX_FILE_IO_EXCEPTION if there was an error writing to the * file. */ @@ -156,8 +157,8 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char /** * @brief Gets the entry for a given key in a property set. * - * @param properties The property set to search. - * @param key The key to search for. + * @param[in] properties The property set to search. + * @param[in] key The key to search for. * @return The entry for the given key, or a default entry with the valueType set to CELIX_PROPERTIES_VALUE_TYPE_UNSET * if the key is not found. */ @@ -166,63 +167,64 @@ celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* pro /** * @brief Gets the value of a property. * - * @param properties The property set to search. - * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set. + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set. * @return The value of the property, or the default value if the property is not set. */ const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); /** * @brief Gets the type of a property value. - * @param properties The property set to search. - * @param key The key of the property to get the type of. + * + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get the type of. * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set. */ celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key); /** - * @brief Sets the value of a property. + * @brief Set the value of a property. * - * - * @param properties The property set to modify. - * @param key The key of the property to set. - * @param value The value to set the property to. + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] value The value to set the property to. */ void celix_properties_set(celix_properties_t* properties, const char* key, const char *value); /** - * @brief Sets the value of a property without copying the key and value strings. + * @brief Set the value of a property without copying the key and value strings. * - * @param properties The property set to modify. - * @param key The key of the property to set. This string will be used directly, so it must not be freed or modified + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or modified * after calling this function. - * @param value The value to set the property to. This string will be used directly, so it must not be freed or + * @param[in] value The value to set the property to. This string will be used directly, so it must not be freed or * modified after calling this function. */ void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); /** - * @brief Unsets a property, removing it from the property set. - * @param properties The property set to modify. - * @param key The key of the property to unset. + * @brief Unset a property, removing it from the property set. + * + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to unset. */ void celix_properties_unset(celix_properties_t* properties, const char *key); /** - * @brief Makes a copy of a property set. + * @brief Make a copy of a property set. * - * @param properties The property set to copy. + * @param[in] properties The property set to copy. * @return A copy of the given property set. */ celix_properties_t* celix_properties_copy(const celix_properties_t* properties); /** - * @brief Gets the value of a property as a long integer. + * @brief Get the value of a property as a long integer. * - * @param properties The property set to search. - * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set, the value is not a long integer, + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set, the value is not a long integer, * or if the value cannot be converted to a long integer. * @return The value of the property as a long integer, or the default value if the property is not set, * the value is not a long integer, or if the value cannot be converted to a long integer. @@ -231,20 +233,20 @@ celix_properties_t* celix_properties_copy(const celix_properties_t* properties); long celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); /** - * @brief Sets the value of a property to a long integer. + * @brief Set the value of a property to a long integer. * - * @param properties The property set to modify. - * @param key The key of the property to set. - * @param value The long value to set the property to. + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] value The long value to set the property to. */ void celix_properties_setLong(celix_properties_t* properties, const char* key, long value); /** - * @brief Gets the value of a property as a boolean. + * @brief Get the value of a property as a boolean. * - * @param properties The property set to search. - * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set, the value is not a boolean, or if the value + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set, the value is not a boolean, or if the value * cannot be converted to a boolean. * @return The value of the property as a boolean, or the default value if the property is not set, the value is not a * boolean, or if the value cannot be converted to a boolean. If the value is a string, it will be converted @@ -253,29 +255,29 @@ void celix_properties_setLong(celix_properties_t* properties, const char* key, l bool celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); /** - * @brief Sets the value of a property to a boolean. + * @brief Set the value of a property to a boolean. * - * @param properties The property set to modify. - * @param key The key of the property to set. - * @param val The boolean value to set the property to. + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] val The boolean value to set the property to. */ void celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); /** - * @brief Sets the value of a property to a double. + * @brief Set the value of a property to a double. * - * @param properties The property set to modify. - * @param key The key of the property to set. - * @param val The double value to set the property to. + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] val The double value to set the property to. */ void celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); /** - * @brief Gets the value of a property as a double. + * @brief Get the value of a property as a double. * - * @param properties The property set to search. - * @param key The key of the property to get. - * @param defaultValue The value to return if the property is not set, the value is not a double, + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set, the value is not a double, * or if the value cannot be converted to a double. * @return The value of the property as a double, or the default value if the property is not set, the value is not * a double, or if the value cannot be converted to a double. If the value is a string, it will be converted @@ -284,7 +286,7 @@ void celix_properties_setDouble(celix_properties_t* properties, const char* key, double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); /** - * @brief Sets the value of a property as a Celix version. + * @brief Set the value of a property as a Celix version. * * This function will make a copy of the provided celix_version_t object and store it in the property set. * @@ -295,7 +297,7 @@ double celix_properties_getAsDouble(const celix_properties_t* properties, const void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); /** - * @brief Sets the value of a property as a Celix version. + * @brief Set the value of a property as a Celix version. * * This function will store a reference to the provided celix_version_t object in the property set and takes * ownership of the provided version. @@ -309,7 +311,7 @@ void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, cons /** - * @brief Gets the value of a property as a Celix version. + * @brief Get the value of a property as a Celix version. * * This function does not convert a string property value to a Celix version automatically. * @@ -325,15 +327,15 @@ const celix_version_t* celix_properties_getVersion( const celix_version_t* defaultValue); /** - * @brief Gets the number of properties in a property set. + * @brief Get the number of properties in a property set. * - * @param properties The property set to get the size of. + * @param[in] properties The property set to get the size of. * @return The number of properties in the property set. */ int celix_properties_size(const celix_properties_t* properties); /** - * @brief Constructs an iterator pointing to the first entry in the properties object. + * @brief Construct an iterator pointing to the first entry in the properties object. * * @param[in] properties The properties object to iterate over. * @return The iterator pointing to the first entry in the properties object. @@ -341,14 +343,25 @@ int celix_properties_size(const celix_properties_t* properties); celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties); /** - * @brief Advances the iterator to the next entry. + * @brief Construct an iterator pointing to the past-the-end entry in the properties object. + * + * This iterator is used to mark the end of the properties object and is not associated with any element in the + * properties object. + * + * @param[in] properties The properties object to iterate over. + * @return The iterator pointing to the past-the-end entry in the properties object. + */ +celix_properties_iterator_t celix_properties_end(const celix_properties_t* properties); + +/** + * @brief Advance the iterator to the next entry. * * @param[in, out] iter The iterator. */ void celix_propertiesIterator_next(celix_properties_iterator_t* iter); /** - * @brief Determines whether the iterator is pointing to an end position. + * @brief Determine whether the iterator is pointing to an end position. * * An iterator is at an end position if it has no more entries to visit. * @@ -358,7 +371,7 @@ void celix_propertiesIterator_next(celix_properties_iterator_t* iter); bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); /** - * @brief Gets the property set being iterated over. + * @brief Get the property set being iterated over. * * @param[in] iter The iterator to get the property set from. * @return The property set being iterated over. @@ -366,7 +379,7 @@ bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter); /** - * @brief Determines whether two iterators are equal. + * @brief Determine whether two iterators are equal. * * @param[in] a The first iterator to compare. * @param[in] b The second iterator to compare. @@ -375,14 +388,14 @@ celix_properties_t* celix_propertiesIterator_properties(const celix_properties_i bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); /** - * @brief Iterates over the entries in the specified celix_properties_t object. + * @brief Iterate over the entries in the specified celix_properties_t object. * * This macro allows you to easily iterate over the entries in a celix_properties_t object. * The loop variable `iterName` will be of type celix_properties_iterator_t and will contain the current * entry during each iteration. * - * @param map The properties object to iterate over. - * @param iterName The name of the iterator variable to use in the loop. + * @param[in] map The properties object to iterate over. + * @param[in] iterName The name of the iterator variable to use in the loop. * * Example usage: * @code{.c} diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 1e5fbe828..c3ae78bc5 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -270,33 +270,41 @@ bool celix_stringHashMap_remove(celix_string_hash_map_t* map, const char* key); void celix_stringHashMap_clear(celix_string_hash_map_t* map); /** - * @brief Returns an iterator pointing to the first element in the map. + * @brief Get an iterator pointing to the first element in the map. * - * @param map The map to get the iterator for. + * @param[in] map The map to get the iterator for. * @return An iterator pointing to the first element in the map. */ celix_string_hash_map_iterator_t celix_stringHashMap_begin(const celix_string_hash_map_t* map); /** - * @brief Check if the iterator is the end of the hash map. + * @brief Get an iterator pointing to the element following the last element in the map. * - * @note the end iterator should not be used to retrieve a key of value. + * @param[in] map The map to get the iterator for. + * @return An iterator pointing to the element following the last element in the map. + */ +celix_string_hash_map_iterator_t celix_stringHashMap_end(const celix_string_hash_map_t* map); + +/** * - * @return true if the iterator is the end. + * @brief Determine if the iterator points to the element following the last element in the map. + * + * @param[in] iter The iterator to check. + * @return true if the iterator points to the element following the last element in the map, false otherwise. */ bool celix_stringHashMapIterator_isEnd(const celix_string_hash_map_iterator_t* iter); /** - * @brief Moves the provided iterator to the next entry in the hash map. + * @brief Advance the iterator to the next element in the map. + * @param[in] iter The iterator to advance. */ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); /** * @brief Marco to loop over all the entries of a string hash map. * - * * Small example of how to use the iterate macro: - * @code + * @code{.c} * celix_string_hash_map_t* map = ... * CELIX_STRING_HASH_MAP_ITERATE(map, iter) { * printf("Visiting hash map entry with key %s\n", inter.key); @@ -313,7 +321,7 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); * @brief Remove the hash map entry for the provided iterator and updates the iterator to the next hash map entry * * Small example of how to use the celix_stringHashMapIterator_remove function: - * @code + * @code{.c} * //remove all even entries from hash map * celix_string_hash_map_t* map = ... * celix_string_hash_map_iterator_t iter = celix_stringHashMap_begin(map); diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 1ef9b75ee..32bd7da35 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -588,6 +588,22 @@ celix_long_hash_map_iterator_t celix_longHashMap_begin(const celix_long_hash_map return iter; } +celix_string_hash_map_iterator_t celix_stringHashMap_end(const celix_string_hash_map_t* map) { + celix_string_hash_map_iterator_t iter; + memset(&iter, 0, sizeof(iter)); + iter.index = map->genericMap.size; + iter._internal[0] = (void*)&map->genericMap; + return iter; +} + +celix_long_hash_map_iterator_t celix_longHashMap_end(const celix_long_hash_map_t* map) { + celix_long_hash_map_iterator_t iter; + memset(&iter, 0, sizeof(iter)); + iter.index = map->genericMap.size; + iter._internal[0] = (void*)&map->genericMap; + return iter; +} + bool celix_stringHashMapIterator_isEnd(const celix_string_hash_map_iterator_t* iter) { return iter->_internal[1] == NULL; //check if entry is NULL } @@ -600,9 +616,9 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; entry = celix_hashMap_nextEntry(map, entry); + iter->index += 1; if (entry != NULL) { iter->_internal[1] = entry; - iter->index += 1; iter->key = entry->key.strKey; iter->value = entry->value; } else { diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 7be3e7135..fbd60715b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -342,6 +342,15 @@ celix_properties_t* celix_properties_create(void) { void celix_properties_destroy(celix_properties_t *props) { if (props != NULL) { + //TODO measure print nr of entries and total size of the string keys and values +// fprintf(stdout, "Properties size; %d", celix_properties_size(props)); +// size_t size = 0; +// CELIX_PROPERTIES_ITERATE(props, iter) { +// size += strlen(iter.entry.key) + 1; +// size += strlen(iter.entry.value) + 1; +// } +// fprintf(stdout, "Properties string size: %zu", size); + celix_stringHashMap_destroy(props->map); free(props); } @@ -448,12 +457,14 @@ static void parseLine(const char* line, celix_properties_t *props) { //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); celix_utils_trimInPlace(key); celix_utils_trimInPlace(value); - celix_properties_setWithoutCopy(props, key, value); +// celix_properties_setWithoutCopy(props, key, value); + celix_properties_set(props, key, value); } else { - free(key); - free(value); +// free(key); +// free(value); } - + free(key); + free(value); } @@ -786,6 +797,18 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro return iter; } +celix_properties_iterator_t celix_properties_end(const celix_properties_t* properties) { + celix_properties_iterator_internal_t internalIter; + internalIter.mapIter = celix_stringHashMap_end(properties->map); + internalIter.props = properties; + + celix_properties_iterator_t iter; + memset(&iter, 0, sizeof(iter)); + iter.index = (int)internalIter.mapIter.index; //TODO make inter.index size_t + memcpy(iter._data, &internalIter, sizeof(internalIter)); + return iter; +} + void celix_propertiesIterator_next(celix_properties_iterator_t *iter) { celix_properties_iterator_internal_t internalIter; memcpy(&internalIter, iter->_data, sizeof(internalIter)); diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index 3d0ee1ab7..72a379426 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -27,6 +27,8 @@ #include "celix_errno.h" #include "version_private.h" +static const char* const CELIX_VERSION_EMPTY_QUALIFIER = ""; + celix_status_t version_createVersion(int major, int minor, int micro, const char * qualifier, version_pt *version) { *version = celix_version_createVersion(major, minor, micro, qualifier); return *version == NULL ? CELIX_ILLEGAL_ARGUMENT : CELIX_SUCCESS; @@ -107,7 +109,8 @@ celix_version_t* celix_version_create(int major, int minor, int micro, const cha if (qualifier == NULL) { qualifier = ""; } - for (int i = 0; i < strlen(qualifier); i++) { + size_t qualifierLen = strlen(qualifier); + for (int i = 0; i < qualifierLen; i++) { char ch = qualifier[i]; if (('A' <= ch) && (ch <= 'Z')) { continue; @@ -125,12 +128,12 @@ celix_version_t* celix_version_create(int major, int minor, int micro, const cha return NULL; } - celix_version_t* version = calloc(1, sizeof(*version)); + celix_version_t* version = malloc(sizeof(*version)); version->major = major; version->minor = minor; version->micro = micro; - version->qualifier = celix_utils_strdup(qualifier); + version->qualifier = qualifierLen == 0 ? (char*)CELIX_VERSION_EMPTY_QUALIFIER : celix_utils_strdup(qualifier); return version; @@ -138,7 +141,9 @@ celix_version_t* celix_version_create(int major, int minor, int micro, const cha void celix_version_destroy(celix_version_t* version) { if (version != NULL) { - free(version->qualifier); + if (version->qualifier != CELIX_VERSION_EMPTY_QUALIFIER) { + free(version->qualifier); + } free(version); } } From efa1017e0ab52cf7d9f5cfb7b840bc7843434bf3 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 7 Jan 2023 20:31:52 +0100 Subject: [PATCH 07/63] Improve test coverage and fix issues in properties and string/long hash map. --- .../utils/gtest/src/CxxPropertiesTestSuite.cc | 41 +++++++-- libs/utils/gtest/src/HashMapTestSuite.cc | 86 ++++++++++++++--- libs/utils/gtest/src/PropertiesTestSuite.cc | 81 ++++++++++++---- libs/utils/include/celix/Properties.h | 73 +++++++-------- libs/utils/include/celix/Version.h | 61 ++++++------ libs/utils/include/celix_long_hash_map.h | 51 ++++++---- libs/utils/include/celix_properties.h | 59 ++++++++---- libs/utils/include/celix_string_hash_map.h | 52 +++++++---- libs/utils/src/celix_hash_map.c | 77 ++++++++++------ libs/utils/src/properties.c | 92 +++++++++++-------- 10 files changed, 450 insertions(+), 223 deletions(-) diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index bc60adbc2..142570c4b 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -52,6 +52,13 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) { EXPECT_EQ(props.getAsBool("key5", false), true); int count = 0; + for (auto it = props.begin(); it != props.end(); ++it) { + EXPECT_NE(it.first, ""); + count++; + } + EXPECT_EQ(5, count); + + count = 0; for (const auto& pair : props) { EXPECT_NE(pair.first, ""); count++; @@ -118,7 +125,7 @@ TEST_F(CxxPropertiesTestSuite, getType) { props.set("long7", (unsigned char)1); //should lead to long; props.set("double1", 1.0); props.set("double2", 1.0f); //set float should lead to double - //TODO version + props.set("version", celix::Version{1, 2, 3}); EXPECT_EQ(props.getType("bool"), celix::Properties::ValueType::Bool); EXPECT_EQ(props.getType("long1"), celix::Properties::ValueType::Long); @@ -130,9 +137,31 @@ TEST_F(CxxPropertiesTestSuite, getType) { EXPECT_EQ(props.getType("long7"), celix::Properties::ValueType::Long); EXPECT_EQ(props.getType("double1"), celix::Properties::ValueType::Double); EXPECT_EQ(props.getType("double2"), celix::Properties::ValueType::Double); - + EXPECT_EQ(props.getType("version"), celix::Properties::ValueType::Version); } +TEST_F(CxxPropertiesTestSuite, testGetAsVersion) { + celix::Properties props; + + // Test getting a version from a string property + props.set("key", "1.2.3"); + celix::Version ver{1, 2, 3}; + EXPECT_TRUE(props.getAsVersion("key") == ver); + + // Test getting a version from a version property + props.set("key", celix::Version{2, 3, 4}); + ver = celix::Version{2, 3, 4}; + EXPECT_EQ(props.getAsVersion("key"), ver); + + // Test getting default value when property is not set + ver = celix::Version{3, 4, 5}; + EXPECT_EQ(props.getAsVersion("non_existent_key", celix::Version{3, 4, 5}), ver); + + // Test getting default value when property value is not a valid version string + props.set("key", "invalid_version_string"); + ver = celix::Version{4, 5, 6}; + EXPECT_EQ(props.getAsVersion("key", celix::Version{4, 5, 6}), ver); +} #if __cplusplus >= 201703L //C++17 or higher TEST_F(CxxPropertiesTestSuite, testStringView) { @@ -183,10 +212,10 @@ TEST_F(CxxPropertiesTestSuite, testStringView) { props.set(key, 1L); //long EXPECT_EQ(1L, props.getAsLong(key, -1)); - props.set(key, 1.0); //double - EXPECT_EQ(1.0, props.getAsDouble(key, -1)); - props.set(key, true); //bool - EXPECT_EQ(true, props.getAsBool(key, false)); + props.set(key, 2.0); //double + EXPECT_EQ(2.0, props.getAsDouble(key, -1)); + props.set(key, false); //bool + EXPECT_EQ(false, props.getAsBool(key, true)); } } diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 85a4944f0..27f0589af 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -221,7 +221,7 @@ TEST_F(HashMapTestSuite, DestroyHashMapWithSimpleRemovedCallback) { celix_longHashMap_destroy(lMap); } -TEST_F(HashMapTestSuite, ClearHashMapWithRemovedCallback) { +TEST_F(HashMapTestSuite, ReplaceAndClearHashMapWithRemovedCallback) { std::atomic count{0}; celix_string_hash_map_create_options_t sOpts{}; sOpts.removedCallbackData = &count; @@ -229,17 +229,19 @@ TEST_F(HashMapTestSuite, ClearHashMapWithRemovedCallback) { auto* c = static_cast*>(data); if (celix_utils_stringEquals(key, "key1")) { c->fetch_add(1); - EXPECT_EQ(value.longValue, 1); + EXPECT_TRUE(value.longValue == 1 || value.longValue == 2); } else if (celix_utils_stringEquals(key, "key2")) { c->fetch_add(1); - EXPECT_EQ(value.longValue, 2); + EXPECT_TRUE(value.longValue == 3 || value.longValue == 4); } }; auto* sMap = celix_stringHashMap_createWithOptions(&sOpts); celix_stringHashMap_putLong(sMap, "key1", 1); - celix_stringHashMap_putLong(sMap, "key2", 2); - celix_stringHashMap_clear(sMap); - EXPECT_EQ(count.load(), 2); + celix_stringHashMap_putLong(sMap, "key1", 2); //replacing old value, count +1 + celix_stringHashMap_putLong(sMap, "key2", 3); + celix_stringHashMap_putLong(sMap, "key2", 4); //replacing old value, count +1 + celix_stringHashMap_clear(sMap); //count +2 + EXPECT_EQ(count.load(), 4); EXPECT_EQ(celix_stringHashMap_size(sMap), 0); celix_stringHashMap_destroy(sMap); @@ -250,17 +252,19 @@ TEST_F(HashMapTestSuite, ClearHashMapWithRemovedCallback) { auto* c = static_cast*>(data); if (key == 1) { c->fetch_add(1); - EXPECT_EQ(value.longValue, 1); + EXPECT_TRUE(value.longValue == 1 || value.longValue == 2); } else if (key == 2) { c->fetch_add(1); - EXPECT_EQ(value.longValue, 2); + EXPECT_TRUE(value.longValue == 3 || value.longValue == 4); } }; auto* lMap = celix_longHashMap_createWithOptions(&lOpts); celix_longHashMap_putLong(lMap, 1, 1); - celix_longHashMap_putLong(lMap, 2, 2); - celix_longHashMap_clear(lMap); - EXPECT_EQ(count.load(), 2); + celix_longHashMap_putLong(lMap, 1, 2); //replacing old value, count +1 + celix_longHashMap_putLong(lMap, 2, 3); + celix_longHashMap_putLong(lMap, 2, 4); //replacing old value, count +1 + celix_longHashMap_clear(lMap); //count +2 + EXPECT_EQ(count.load(), 4); EXPECT_EQ(celix_longHashMap_size(lMap), 0); celix_longHashMap_destroy(lMap); } @@ -518,7 +522,7 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { auto iter1 = celix_stringHashMap_begin(sMap); while (!celix_stringHashMapIterator_isEnd(&iter1)) { if (iter1.index % 2 == 0) { - //note only removing entries where the iter index is even + //note only removing entries where the iter key is even celix_stringHashMapIterator_remove(&iter1); } else { celix_stringHashMapIterator_next(&iter1); @@ -567,10 +571,64 @@ TEST_F(HashMapTestSuite, IterateEndTest) { EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1)); EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter2)); - //TODO loop and test with index. - celix_stringHashMap_destroy(sMap1); celix_stringHashMap_destroy(sMap2); celix_longHashMap_destroy(lMap1); celix_longHashMap_destroy(lMap2); } + + +TEST_F(HashMapTestSuite, EqualsTest) { + auto* sMap = createStringHashMap(2); + auto sIter1 = celix_stringHashMap_begin(sMap); + auto sIter2 = celix_stringHashMap_begin(sMap); + + // Test equal iterators + EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2)); + + // Test unequal iterators after only 1 modification + celix_stringHashMapIterator_next(&sIter1); + EXPECT_FALSE(celix_stringHashMapIterator_equals(&sIter1, &sIter2)); + + // Test equal iterators after both modification + celix_stringHashMapIterator_next(&sIter2); + EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2)); + + + //Same for long hash map + auto *lMap = createLongHashMap(1); + auto lIter1 = celix_longHashMap_begin(lMap); + auto lIter2 = celix_longHashMap_begin(lMap); + + // Test equal iterators + EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2)); + + // Test unequal iterators after only 1 modification + celix_longHashMapIterator_next(&lIter1); + EXPECT_FALSE(celix_longHashMapIterator_equals(&lIter1, &lIter2)); + + // Test equal iterators after both modification + celix_longHashMapIterator_next(&lIter2); + EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2)); + + celix_stringHashMap_destroy(sMap); + celix_longHashMap_destroy(lMap); +} + +TEST_F(HashMapTestSuite, EqualsZeroSizeMapTest) { + // Because map size is 0, begin iter should equal end iter + auto* sMap = createStringHashMap(0); + auto sIter1 = celix_stringHashMap_begin(sMap); + auto sEnd = celix_stringHashMap_end(sMap); + EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sEnd)); + + + // Because map size is 0, begin iter should equal end iter + auto *lMap = createLongHashMap(0); + auto lIter1 = celix_longHashMap_begin(lMap); + auto lEnd = celix_longHashMap_end(lMap); + EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lEnd)); + + celix_stringHashMap_destroy(sMap); + celix_longHashMap_destroy(lMap); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index b9b6eb95d..74e96f6e7 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -246,6 +246,29 @@ TEST_F(PropertiesTestSuite, fillTest) { celix_properties_destroy(props); } +TEST_F(PropertiesTestSuite, setOverwrite) { + auto* props = celix_properties_create(); + auto* version = celix_version_createEmptyVersion(); + const char* key = "key"; + + celix_properties_set(props, key, "str1"); + EXPECT_STREQ("str1", celix_properties_get(props, key, "")); + celix_properties_set(props, key, "str2"); + EXPECT_STREQ("str2", celix_properties_get(props, key, "")); + celix_properties_setLong(props, key, 1); + EXPECT_EQ(1, celix_properties_getAsLong(props, key, -1L)); + celix_properties_setDouble(props, key, 2.0); + EXPECT_EQ(2.0, celix_properties_getAsLong(props, key, -2.0)); + celix_properties_setBool(props, key, false); + EXPECT_EQ(false, celix_properties_getAsBool(props, key, true)); + celix_properties_setVersionWithoutCopy(props, key, version); + EXPECT_EQ(version, celix_properties_getVersion(props, key, nullptr)); + celix_properties_set(props, key, "last"); + + celix_properties_destroy(props); +} + + TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { celix_properties_t *props = celix_properties_create(); @@ -297,35 +320,35 @@ TEST_F(PropertiesTestSuite, getEntry) { auto* version = celix_version_createVersion(1, 2, 3, nullptr); celix_properties_setVersion(props, "key5", version); - auto entry = celix_properties_getEntry(props, "key1"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, entry.valueType); - EXPECT_STREQ("value1", entry.value); - EXPECT_STREQ("value1", entry.typed.strValue); + auto* entry = celix_properties_getEntry(props, "key1"); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, entry->valueType); + EXPECT_STREQ("value1", entry->value); + EXPECT_STREQ("value1", entry->typed.strValue); entry = celix_properties_getEntry(props, "key2"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, entry.valueType); - EXPECT_STREQ("123", entry.value); - EXPECT_EQ(123, entry.typed.longValue); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, entry->valueType); + EXPECT_STREQ("123", entry->value); + EXPECT_EQ(123, entry->typed.longValue); entry = celix_properties_getEntry(props, "key3"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, entry.valueType); - EXPECT_NE(strstr(entry.value, "123.456"), nullptr); - EXPECT_DOUBLE_EQ(123.456, entry.typed.doubleValue); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, entry->valueType); + EXPECT_NE(strstr(entry->value, "123.456"), nullptr); + EXPECT_DOUBLE_EQ(123.456, entry->typed.doubleValue); entry = celix_properties_getEntry(props, "key4"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_BOOL, entry.valueType); - EXPECT_STREQ("true", entry.value); - EXPECT_TRUE(entry.typed.boolValue); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_BOOL, entry->valueType); + EXPECT_STREQ("true", entry->value); + EXPECT_TRUE(entry->typed.boolValue); entry = celix_properties_getEntry(props, "key5"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, entry.valueType); - EXPECT_STREQ("1.2.3", entry.value); - EXPECT_EQ(1, celix_version_getMajor(entry.typed.versionValue)); - EXPECT_EQ(2, celix_version_getMinor(entry.typed.versionValue)); - EXPECT_EQ(3, celix_version_getMicro(entry.typed.versionValue)); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, entry->valueType); + EXPECT_STREQ("1.2.3", entry->value); + EXPECT_EQ(1, celix_version_getMajor(entry->typed.versionValue)); + EXPECT_EQ(2, celix_version_getMinor(entry->typed.versionValue)); + EXPECT_EQ(3, celix_version_getMicro(entry->typed.versionValue)); entry = celix_properties_getEntry(props, "key6"); - EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, entry.valueType); + EXPECT_EQ(nullptr, entry); celix_version_destroy(version); celix_properties_destroy(props); @@ -436,6 +459,26 @@ TEST_F(PropertiesTestSuite, getVersion) { EXPECT_EQ(celix_version_getMicro(actual), 3); EXPECT_STREQ(celix_version_getQualifier(actual), ""); + + // Test getAsVersion + celix_properties_set(properties, "string_version", "1.1.1"); + auto* ver1 = celix_properties_getAsVersion(properties, "non-existing", emptyVersion); + auto* ver2 = celix_properties_getAsVersion(properties, "non-existing", nullptr); + auto* ver3 = celix_properties_getAsVersion(properties, "string_version", emptyVersion); + auto* ver4 = celix_properties_getAsVersion(properties, "key", emptyVersion); + EXPECT_NE(ver1, nullptr); + EXPECT_EQ(ver2, nullptr); + EXPECT_EQ(celix_version_getMajor(ver3), 1); + EXPECT_EQ(celix_version_getMinor(ver3), 1); + EXPECT_EQ(celix_version_getMicro(ver3), 1); + EXPECT_EQ(celix_version_getMajor(ver4), 1); + EXPECT_EQ(celix_version_getMinor(ver4), 2); + EXPECT_EQ(celix_version_getMicro(ver4), 3); + celix_version_destroy(ver1); + celix_version_destroy(ver3); + celix_version_destroy(ver4); + + celix_version_destroy(emptyVersion); celix_properties_destroy(properties); } diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index c75119a3c..45bb0c8be 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -33,35 +33,32 @@ namespace celix { /** * @brief A iterator for celix::Properties. */ - class PropertiesIterator { + class ConstPropertiesIterator { public: - explicit PropertiesIterator(const celix_properties_t* props) { + explicit ConstPropertiesIterator(const celix_properties_t* props) { iter = celix_properties_begin(props); setFields(); } - explicit PropertiesIterator(celix_properties_iterator_t _iter) { - iter = std::move(_iter); + explicit ConstPropertiesIterator(celix_properties_iterator_t _iter) { + iter = _iter; setFields(); } - PropertiesIterator& operator++() { + ConstPropertiesIterator& operator++() { next(); return *this; } - PropertiesIterator& operator*() { + const ConstPropertiesIterator& operator*() { return *this; } - bool operator==(const celix::PropertiesIterator& rhs) const { - if (end || rhs.end) { - return end && rhs.end; - } + bool operator==(const celix::ConstPropertiesIterator& rhs) const { return celix_propertiesIterator_equals(&iter, &rhs.iter); } - bool operator!=(const celix::PropertiesIterator& rhs) const { + bool operator!=(const celix::ConstPropertiesIterator& rhs) const { return !operator==(rhs); } @@ -84,7 +81,6 @@ namespace celix { } celix_properties_iterator_t iter{.index = -1, .entry = {}, ._data = {}}; - bool end{false}; }; @@ -96,7 +92,7 @@ namespace celix { */ class Properties { public: - using const_iterator = PropertiesIterator; + using const_iterator = ConstPropertiesIterator; //note currently only a const iterator is supported. /** * @brief Enum representing the possible types of a property value. @@ -245,28 +241,28 @@ namespace celix { * @brief begin iterator */ [[nodiscard]] const_iterator begin() const noexcept { - return PropertiesIterator{cProps.get()}; + return ConstPropertiesIterator{cProps.get()}; } /** * @brief end iterator */ [[nodiscard]] const_iterator end() const noexcept { - return PropertiesIterator{celix_properties_end(cProps.get())}; + return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } /** * @brief constant begin iterator */ [[nodiscard]] const_iterator cbegin() const noexcept { - return PropertiesIterator{cProps.get()}; + return ConstPropertiesIterator{cProps.get()}; } /** * @brief constant end iterator */ [[nodiscard]] const_iterator cend() const noexcept { - return PropertiesIterator{celix_properties_end(cProps.get())}; + return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } #if __cplusplus >= 201703L //C++17 or higher @@ -317,23 +313,22 @@ namespace celix { /** * @brief Get the value of the property with key as a Celix version. * - * Note that this function does not automatically convert a string property value to a Celix version. - * * @param[in] key The key of the property to get. - * @param[in] defaultValue The value to return if the property is not set or if the value is not a Celix - * version. - * @return The value of the property if it is a Celix version, or the default value if the property is not set - * or the value is not a Celix version. + * @param[in] defaultValue The value to return if the property is not set or if the value cannot be converted + * to a Celix version. + * @return The Celix version value of the property if it exists and can be converted, + * or the default value otherwise. */ - //TODO test - [[nodiscard]] celix::Version getVersion(std::string_view key, celix::Version defaultValue = {}) { - auto* cVersion = celix_properties_getVersion(cProps.get(), key.data(), nullptr); + [[nodiscard]] celix::Version getAsVersion(std::string_view key, celix::Version defaultValue = {}) { + auto* cVersion = celix_properties_getAsVersion(cProps.get(), key.data(), nullptr); if (cVersion) { - return celix::Version{ + celix::Version version{ celix_version_getMajor(cVersion), celix_version_getMinor(cVersion), celix_version_getMicro(cVersion), celix_version_getQualifier(cVersion)}; + celix_version_destroy(cVersion); + return version; } return defaultValue; } @@ -345,7 +340,6 @@ namespace celix { * @return The type of the property with the given key, or ValueType::Unset if the property * does not exist. */ - //TODO test [[nodiscard]] ValueType getType(std::string_view key) { return getAndConvertType(cProps, key.data()); } @@ -361,20 +355,21 @@ namespace celix { */ template void set(std::string_view key, T&& value) { - if constexpr (std::is_same_v, bool>) { + using DecayedT = std::decay_t; + if constexpr (std::is_same_v) { celix_properties_setBool(cProps.get(), key.data(), value); - } else if constexpr (std::is_same_v, std::string_view>) { + } else if constexpr (std::is_same_v) { celix_properties_set(cProps.get(), key.data(), value.data()); } else if constexpr (std::is_convertible_v) { std::string_view view{value}; celix_properties_set(cProps.get(), key.data(), view.data()); - } else if constexpr (std::is_same_v, bool>) { + } else if constexpr (std::is_same_v) { celix_properties_setBool(cProps.get(), key.data(), value); - } else if constexpr (std::is_convertible_v, long>) { + } else if constexpr (std::is_integral_v and std::is_convertible_v) { celix_properties_setLong(cProps.get(), key.data(), value); - } else if constexpr (std::is_convertible_v, double>) { + } else if constexpr (std::is_convertible_v) { celix_properties_setDouble(cProps.get(), key.data(), value); - } else if constexpr (std::is_same_v, celix::Version>) { + } else if constexpr (std::is_same_v) { celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); } else if constexpr (std::is_same_v) { celix_properties_setVersion(cProps.get(), key.data(), value); @@ -439,16 +434,18 @@ namespace celix { * @return The value of the property if it is a Celix version, or the default value if the property is not set * or the value is not a Celix version. */ - [[nodiscard]] celix::Version getVersion(const std::string& key, celix::Version defaultValue = {}) { - auto* cVersion = celix_properties_getVersion(cProps.get(), key.data(), nullptr); + [[nodiscard]] celix::Version getAsVersion(const std::string& key, celix::Version defaultValue = {}) { + auto* cVersion = celix_properties_getAsVersion(cProps.get(), key.data(), nullptr); if (cVersion) { - return celix::Version{ + celix::Version version{ celix_version_getMajor(cVersion), celix_version_getMinor(cVersion), celix_version_getMicro(cVersion), celix_version_getQualifier(cVersion)}; + celix_version_destroy(cVersion); + return version; } - return defaultValue; + return defaultValue } /** diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h index ce70d8d4c..ef50be0f9 100644 --- a/libs/utils/include/celix/Version.h +++ b/libs/utils/include/celix/Version.h @@ -28,40 +28,40 @@ namespace celix { //TODO CxxVersionTestSuite //TODO doxygen + //TODO test in unordered map and set + //TODO test in map and set class Version { public: - Version() : cVersion{createVersion(celix_version_createEmptyVersion())} {} + Version() : + cVersion{createVersion(celix_version_createEmptyVersion())}, + qualifier{celix_version_getQualifier(cVersion.get())} {} + #if __cplusplus >= 201703L //C++17 or higher explicit Version(int major, int minor, int micro, std::string_view qualifier = {}) : - cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))} {} + cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))}, + qualifier{celix_version_getQualifier(cVersion.get())} {} #else - explicit Version(int major, int minor, int micro, const& std::string qualifier = {}) : - cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))} {} + explicit Version(int major, int minor, int micro, const std::string& qualifier = {}) : + cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))}, + qualifier{celix_version_getQualifier(cVersion.get())} {} #endif Version(Version&&) = default; + Version(const Version& rhs) = default; Version& operator=(Version&&) = default; + Version& operator=(const Version& rhs) = default; - Version(const Version& rhs) : cVersion{createVersion(celix_version_copy(rhs.cVersion.get()))} {} - - Version& operator=(const Version& rhs) { - if (this != &rhs) { - cVersion = createVersion(rhs.cVersion.get()); - } - return *this; - } - - bool operator==(const Version& rhs) { + bool operator==(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) == 0; } - bool operator<(const Version& rhs) { + bool operator<(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) < 0; } - //TODO rest of the operators + //TODO rest of the operators, is that needed? /** * @brief Warps a C Celix Version to a C++ Celix Version, but takes no ownership. @@ -81,28 +81,24 @@ namespace celix { return cVersion.get(); } + //TODO doc [[nodiscard]] int getMajor() const { return celix_version_getMajor(cVersion.get()); } + //TODO doc [[nodiscard]] int getMinor() const { return celix_version_getMinor(cVersion.get()); } + //TODO doc [[nodiscard]] int getMicro() const { return celix_version_getMicro(cVersion.get()); } - [[nodiscard]] std::string getQualifier() const { - return std::string{celix_version_getQualifier(cVersion.get())}; - } - - /** - * @brief Return whether the version is an empty version (0.0.0.""). - */ - [[nodiscard]] bool emptyVersion() const { - //TODO celix_version_isEmpty(cVersion.get()); - return false; + //TODO doc + [[nodiscard]] const std::string& getQualifier() const { + return qualifier; } private: static std::shared_ptr createVersion(celix_version_t* cVersion) { @@ -117,5 +113,18 @@ namespace celix { explicit Version(celix_version_t* v) : cVersion{v, [](celix_version_t *){/*nop*/}} {} std::shared_ptr cVersion; + std::string qualifier; //cached qualifier of the const char* from celix_version_getQualifier + }; +} + +namespace std { + template<> + struct hash { + size_t operator()(const celix::Version& v) const { + return std::hash()(v.getMajor()) ^ + std::hash()(v.getMinor()) ^ + std::hash()(v.getMicro()) ^ + std::hash()(v.getQualifier()); + } }; } \ No newline at end of file diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 40e23bffd..c0c36968c 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -159,12 +159,15 @@ void celix_longHashMap_destroy(celix_long_hash_map_t* map); size_t celix_longHashMap_size(const celix_long_hash_map_t* map); /** - * @brief add pointer entry the string hash map. + * @brief Add pointer entry the string hash map. * - * @param map The hashmap - * @param key The key to use. - * @param value The value to store with the key - * @return The previous key or NULL of no key was set. Note also returns NULL if the previous value for the key was NULL. + * @note The returned previous value can be already freed by a removed callback (if configured). + * + * @param[in] map The hashmap + * @param[in] key The key to use. + * @param[in] value The value to store with the key + * @return The previous value or NULL of no value was set for th provided key. + * Note also returns NULL if the previous value for the key was NULL. */ void* celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value); @@ -292,21 +295,14 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter) void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); /** - * @brief Marco to loop over all the entries of a long hash map. - * - * Small example of how to use the iterate macro: - * @code{.c} - * celix_long_hash_map_t* map = ... - * CELIX_LONG_HASH_MAP_ITERATE(map, iter) { - * printf("Visiting hash map entry with key %li\n", inter.key); - * } - * @endcode - * - * @param map The (const celix_long_hash_map_t*) map to iterate over. - * @param iterName A iterName which will be of type celix_long_hash_map_iterator_t to hold the iterator. + * @brief Compares two celix_long_hash_map_iterator_t objects for equality. + * @param[in] iterator The first iterator to compare. + * @param[in] other The second iterator to compare. + * @return true if the iterators point to the same entry in the same hash map, false otherwise. */ -#define CELIX_LONG_HASH_MAP_ITERATE(map, iterName) \ - for (celix_long_hash_map_iterator_t iterName = celix_longHashMap_begin(map); !celix_longHashMapIterator_isEnd(&(iterName)); celix_longHashMapIterator_next(&(iterName))) +bool celix_longHashMapIterator_equals( + const celix_long_hash_map_iterator_t* iterator, + const celix_long_hash_map_iterator_t* other); /** * @brief Remove the hash map entry for the provided iterator and updates the iterator to the next hash map entry @@ -327,6 +323,23 @@ void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter); */ void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter); +/** + * @brief Marco to loop over all the entries of a long hash map. + * + * Small example of how to use the iterate macro: + * @code{.c} + * celix_long_hash_map_t* map = ... + * CELIX_LONG_HASH_MAP_ITERATE(map, iter) { + * printf("Visiting hash map entry with key %li\n", inter.key); + * } + * @endcode + * + * @param map The (const celix_long_hash_map_t*) map to iterate over. + * @param iterName A iterName which will be of type celix_long_hash_map_iterator_t to hold the iterator. + */ +#define CELIX_LONG_HASH_MAP_ITERATE(map, iterName) \ + for (celix_long_hash_map_iterator_t iterName = celix_longHashMap_begin(map); !celix_longHashMapIterator_isEnd(&(iterName)); celix_longHashMapIterator_next(&(iterName))) + #ifdef __cplusplus } #endif diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 7794661ec..f20a2fe85 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -22,10 +22,17 @@ * @brief Header file for the Celix Properties API. * * The Celix Properties API provides a means for storing and manipulating key-value pairs, called properties, - * which can be used to store configuration data or metadata for a services, components, or framework configuration. + * which can be used to store configuration data or metadata for a service, component, or framework configuration. * Functions are provided for creating and destroying property sets, loading and storing properties from/to a file * or stream, and setting, getting, and unsetting individual properties. There are also functions for converting * property values to various types (e.g. long, bool, double) and for iterating over the properties in a set. + * + * Supported property value types include: + * - string (char*) + * - long + * - double + * - bool + * - celix_version_t* */ #include @@ -41,8 +48,7 @@ extern "C" { #endif /** - * @brief celix_properties_t is a type that represents a set of key-value pairs called properties, - * which can be used to store configuration data or metadata for a services, components or framework configuration. + * @brief celix_properties_t is a type that represents a set of key-value pairs called properties. * * @note Not thread safe. */ @@ -102,21 +108,21 @@ typedef struct celix_properties_iterator { } celix_properties_iterator_t; /** - * @brief Creates a new empty property set. + * @brief Create a new empty property set. * * @return A new empty property set. */ celix_properties_t* celix_properties_create(void); /** - * @brief Destroys a property set, freeing all associated resources. + * @brief Destroy a property set, freeing all associated resources. * * @param[in] properties The property set to destroy. If properties is NULL, this function will do nothing. */ void celix_properties_destroy(celix_properties_t* properties); /** - * @brief Loads properties from a file. + * @brief Load properties from a file. * * @param[in] filename The name of the file to load properties from. * @return A property set containing the properties from the file. @@ -126,7 +132,7 @@ celix_properties_t* celix_properties_load(const char *filename); /** - * @brief Loads properties from a stream. + * @brief Load properties from a stream. * * @param[in,out] stream The stream to load properties from. * @return A property set containing the properties from the stream. @@ -135,7 +141,7 @@ celix_properties_t* celix_properties_load(const char *filename); celix_properties_t* celix_properties_loadWithStream(FILE *stream); /** - * @brief Loads properties from a string. + * @brief Load properties from a string. * * @param[in] input The string to load properties from. * @return A property set containing the properties from the string. @@ -144,7 +150,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE *stream); celix_properties_t* celix_properties_loadFromString(const char *input); /** - * @brief Stores properties to a file. + * @brief Store properties to a file. * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. @@ -155,17 +161,16 @@ celix_properties_t* celix_properties_loadFromString(const char *input); celix_status_t celix_properties_store(celix_properties_t* properties, const char* file, const char* header); /** - * @brief Gets the entry for a given key in a property set. + * @brief Get the entry for a given key in a property set. * * @param[in] properties The property set to search. * @param[in] key The key to search for. - * @return The entry for the given key, or a default entry with the valueType set to CELIX_PROPERTIES_VALUE_TYPE_UNSET - * if the key is not found. + * @return The entry for the given key, or a NULL if the key is not found. */ -celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* properties, const char* key); +celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, const char* key); /** - * @brief Gets the value of a property. + * @brief Get the value of a property. * * @param[in] properties The property set to search. * @param[in] key The key of the property to get. @@ -175,7 +180,7 @@ celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* pro const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); /** - * @brief Gets the type of a property value. + * @brief Get the type of a property value. * * @param[in] properties The property set to search. * @param[in] key The key of the property to get the type of. @@ -311,7 +316,7 @@ void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, cons /** - * @brief Get the value of a property as a Celix version. + * @brief Get the Celix version value of a property. * * This function does not convert a string property value to a Celix version automatically. * @@ -326,6 +331,28 @@ const celix_version_t* celix_properties_getVersion( const char* key, const celix_version_t* defaultValue); +/** + * @brief Get the value of a property as a Celix version. + * + * If the property value is a Celix version, a copy of this version will be returned. + * If the property value is a string, this function will attempt to convert it to a new Celix version. + * If the property is not set or is not a valid Celix version string, a copy of the provided defaultValue is returned. + * + * @note The caller is responsible for deallocating the memory of the returned version. + * + * @param[in] properties The property set to search. + * @param[in] key The key of the property to get. + * @param[in] defaultValue The value to return if the property is not set or if the value is not a Celix version. + * @return A copy of the property value if it is a Celix version, or a new Celix version created from the property + * value string if it can be converted, or a copy of the default value if the property is not set or the value + * is not a valid Celix version. + * @retval NULL if version cannot be found/converted and the defaultValue is NULL. + */ +celix_version_t* celix_properties_getAsVersion( + const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue); + /** * @brief Get the number of properties in a property set. * diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index c3ae78bc5..323755cb6 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -169,12 +169,15 @@ void celix_stringHashMap_destroy(celix_string_hash_map_t* map); size_t celix_stringHashMap_size(const celix_string_hash_map_t* map); /** - * @brief add pointer entry the string hash map. + * @brief Add pointer entry the string hash map. * - * @param map The hashmap. - * @param key The key to use. The hashmap will create a copy if needed. - * @param value The value to store with the key. - * @return The previous key or NULL of no key was set. Note also returns NULL if the previous value for the key was NULL. + * @note The returned previous value can be already freed by a removed callback (if configured). + * + * @param[in] map The hashmap. + * @param[in] key The key to use. The hashmap will create a copy if needed. + * @param[in] value The value to store with the key. + * @return The previous value or NULL of no value was set for th provided key. + * Note also returns NULL if the previous value for the key was NULL. */ void* celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value); @@ -301,21 +304,14 @@ bool celix_stringHashMapIterator_isEnd(const celix_string_hash_map_iterator_t* i void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); /** - * @brief Marco to loop over all the entries of a string hash map. - * - * Small example of how to use the iterate macro: - * @code{.c} - * celix_string_hash_map_t* map = ... - * CELIX_STRING_HASH_MAP_ITERATE(map, iter) { - * printf("Visiting hash map entry with key %s\n", inter.key); - * } - * @endcode - * - * @param map The (const celix_string_hash_map_t*) map to iterate over. - * @param iterName A iterName which will be of type celix_string_hash_map_iterator_t to hold the iterator. + * @brief Compares two celix_string_hash_map_iterator_t objects for equality. + * @param[in] iterator The first iterator to compare. + * @param[in] other The second iterator to compare. + * @return true if the iterators point to the same entry in the same hash map, false otherwise. */ -#define CELIX_STRING_HASH_MAP_ITERATE(map, iterName) \ - for (celix_string_hash_map_iterator_t iterName = celix_stringHashMap_begin(map); !celix_stringHashMapIterator_isEnd(&(iterName)); celix_stringHashMapIterator_next(&(iterName))) +bool celix_stringHashMapIterator_equals( + const celix_string_hash_map_iterator_t* iterator, + const celix_string_hash_map_iterator_t* other); /** * @brief Remove the hash map entry for the provided iterator and updates the iterator to the next hash map entry @@ -336,6 +332,24 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); */ void celix_stringHashMapIterator_remove(celix_string_hash_map_iterator_t* iter); +/** + * @brief Marco to loop over all the entries of a string hash map. + * + * Small example of how to use the iterate macro: + * @code{.c} + * celix_string_hash_map_t* map = ... + * CELIX_STRING_HASH_MAP_ITERATE(map, iter) { + * printf("Visiting hash map entry with key %s\n", inter.key); + * } + * @endcode + * + * @param map The (const celix_string_hash_map_t*) map to iterate over. + * @param iterName A iterName which will be of type celix_string_hash_map_iterator_t to hold the iterator. + */ +#define CELIX_STRING_HASH_MAP_ITERATE(map, iterName) \ + for (celix_string_hash_map_iterator_t iterName = celix_stringHashMap_begin(map); !celix_stringHashMapIterator_isEnd(&(iterName)); celix_stringHashMapIterator_next(&(iterName))) + + #ifdef __cplusplus } #endif diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 32bd7da35..42e37ec62 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -224,6 +224,24 @@ static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { } #endif +static void celix_hashMap_callRemovedCallback(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { + if (map->simpleRemovedCallback) { + map->simpleRemovedCallback(removedEntry->value.ptrValue); + } else if (map->removedLongKeyCallback) { + map->removedLongKeyCallback(map->removedCallbackData, removedEntry->key.longKey, removedEntry->value); + } else if (map->removedStringKeyCallback) { + map->removedStringKeyCallback(map->removedCallbackData, removedEntry->key.strKey, removedEntry->value); + } +} + +static void celix_hashMap_destroyRemovedEntry(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { + celix_hashMap_callRemovedCallback(map, removedEntry); + if (map->keyType == CELIX_HASH_MAP_STRING_KEY && !map->storeKeysWeakly) { + free((char*)removedEntry->key.strKey); + } + free(removedEntry); +} + static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value, unsigned int bucketIndex) { celix_hash_map_entry_t* entry = map->buckets[bucketIndex]; celix_hash_map_entry_t* newEntry = malloc(sizeof(*newEntry)); @@ -256,6 +274,7 @@ static bool celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, lo if (replacedValueOut != NULL) { *replacedValueOut = entry->value; } + celix_hashMap_callRemovedCallback(map, entry); memcpy(&entry->value, value, sizeof(*value)); return true; } @@ -293,20 +312,6 @@ static bool celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, lon return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static void celix_hashMap_destroyRemovedEntry(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { - if (map->simpleRemovedCallback) { - map->simpleRemovedCallback(removedEntry->value.ptrValue); - } else if (map->removedLongKeyCallback) { - map->removedLongKeyCallback(map->removedCallbackData, removedEntry->key.longKey, removedEntry->value); - } else if (map->removedStringKeyCallback) { - map->removedStringKeyCallback(map->removedCallbackData, removedEntry->key.strKey, removedEntry->value); - } - if (map->keyType == CELIX_HASH_MAP_STRING_KEY && !map->storeKeysWeakly) { - free((char*)removedEntry->key.strKey); - } - free(removedEntry); -} - static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long longKey) { celix_hash_map_key_t key; if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { @@ -590,17 +595,21 @@ celix_long_hash_map_iterator_t celix_longHashMap_begin(const celix_long_hash_map celix_string_hash_map_iterator_t celix_stringHashMap_end(const celix_string_hash_map_t* map) { celix_string_hash_map_iterator_t iter; - memset(&iter, 0, sizeof(iter)); - iter.index = map->genericMap.size; iter._internal[0] = (void*)&map->genericMap; + iter._internal[1] = NULL; + iter.index = map->genericMap.size; + iter.key = ""; + memset(&iter.value, 0, sizeof(iter.value)); return iter; } celix_long_hash_map_iterator_t celix_longHashMap_end(const celix_long_hash_map_t* map) { celix_long_hash_map_iterator_t iter; - memset(&iter, 0, sizeof(iter)); - iter.index = map->genericMap.size; iter._internal[0] = (void*)&map->genericMap; + iter._internal[1] = NULL; + iter.index = map->genericMap.size; + iter.key = 0L; + memset(&iter.value, 0, sizeof(iter.value)); return iter; } @@ -615,33 +624,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter) void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; - entry = celix_hashMap_nextEntry(map, entry); iter->index += 1; - if (entry != NULL) { + entry = celix_hashMap_nextEntry(map, entry); + if (entry) { iter->_internal[1] = entry; iter->key = entry->key.strKey; iter->value = entry->value; } else { - memset(iter, 0, sizeof(*iter)); - iter->_internal[0] = (void*)map; + iter->_internal[1] = NULL; + iter->key = NULL; + memset(&iter->value, 0, sizeof(iter->value)); } } void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; + iter->index += 1; entry = celix_hashMap_nextEntry(map, entry); - if (entry != NULL) { + if (entry) { iter->_internal[1] = entry; - iter->index += 1; iter->key = entry->key.longKey; iter->value = entry->value; } else { - memset(iter, 0, sizeof(*iter)); - iter->_internal[0] = (void*)map; + iter->_internal[1] = NULL; + iter->key = 0L; + memset(&iter->value, 0, sizeof(iter->value)); } } +bool celix_stringHashMapIterator_equals( + const celix_string_hash_map_iterator_t* iterator, + const celix_string_hash_map_iterator_t* other) { + return iterator->_internal[0] == other->_internal[0] /* same map */ && + iterator->_internal[1] == other->_internal[1] /* same entry */; +} + +bool celix_longHashMapIterator_equals( + const celix_long_hash_map_iterator_t* iterator, + const celix_long_hash_map_iterator_t* other) { + return iterator->_internal[0] == other->_internal[0] /* same map */ && + iterator->_internal[1] == other->_internal[1] /* same entry */; +} + void celix_stringHashMapIterator_remove(celix_string_hash_map_iterator_t* iter) { celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index fbd60715b..422a2bb70 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -23,13 +23,12 @@ #include #include #include -#include #include #include #include +#include #include "celix_build_assert.h" -#include "utils.h" //TODO try to remove #include "celix_utils.h" #include "celix_string_hash_map.h" @@ -68,7 +67,7 @@ struct celix_properties { #define MALLOC_BLOCK_SIZE 5 -static void parseLine(const char* line, celix_properties_t *props); +static void celix_properties_parseLine(const char* line, celix_properties_t *props); properties_pt properties_create(void) { return celix_properties_create(); @@ -292,7 +291,7 @@ static void celix_properties_createAndSetEntry( celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, boolValue, versionValue); if (entry != NULL) { - celix_stringHashMap_put(properties->map, key, entry); + celix_stringHashMap_put(properties->map, entry->key, entry); } } @@ -330,7 +329,7 @@ celix_properties_t* celix_properties_create(void) { if (props != NULL) { celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; opts.storeKeysWeakly = true; - opts.initialCapacity = CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE; + opts.initialCapacity = (unsigned int)ceil(CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE / 0.75); opts.removedCallbackData = props; opts.removedCallback = celix_properties_removeEntryCallback; props->map = celix_stringHashMap_createWithOptions(&opts); @@ -366,7 +365,7 @@ celix_properties_t* celix_properties_load(const char *filename) { return props; } -static void parseLine(const char* line, celix_properties_t *props) { +static void celix_properties_parseLine(const char* line, celix_properties_t *props) { int linePos = 0; bool precedingCharIsBackslash = false; bool isComment = false; @@ -454,18 +453,13 @@ static void parseLine(const char* line, celix_properties_t *props) { } if (!isComment) { - //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); celix_utils_trimInPlace(key); celix_utils_trimInPlace(value); -// celix_properties_setWithoutCopy(props, key, value); - celix_properties_set(props, key, value); + celix_properties_setWithoutCopy(props, key, value); } else { -// free(key); -// free(value); + free(key); + free(value); } - free(key); - free(value); - } celix_properties_t* celix_properties_loadWithStream(FILE *file) { @@ -473,6 +467,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE *file) { return NULL; } + //TODO create properties with no internal short properties buffer, so celix_properties_createWithOptions() celix_properties_t *props = celix_properties_create(); if (props == NULL) { return NULL; @@ -500,7 +495,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE *file) { char* savePtr = NULL; char* line = strtok_r(fileBuffer, "\n", &savePtr); while (line != NULL) { - parseLine(line, props); + celix_properties_parseLine(line, props); line = strtok_r(NULL, "\n", &savePtr); } free(fileBuffer); @@ -528,7 +523,7 @@ celix_properties_t* celix_properties_loadFromString(const char *input) { break; } - parseLine(line, props); + celix_properties_parseLine(line, props); } while(line != NULL); free(in); @@ -611,19 +606,19 @@ celix_properties_value_type_e celix_properties_getType(const celix_properties_t* } const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { - celix_properties_entry_t* entry = NULL; - if (properties != NULL) { - entry = celix_stringHashMap_get(properties->map, key); + celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); + if (entry != NULL) { + return entry->value; } - return entry == NULL ? defaultValue : entry->value; + return defaultValue; } -celix_properties_entry_t celix_properties_getEntry(const celix_properties_t* properties, const char* key) { - celix_properties_entry_t invalidEntry; - memset(&invalidEntry, 0, sizeof(invalidEntry)); - invalidEntry.valueType = CELIX_PROPERTIES_VALUE_TYPE_UNSET; - celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); - return entry == NULL ? invalidEntry : *entry; +celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, const char* key) { + celix_properties_entry_t* entry = NULL; + if (properties) { + entry = celix_stringHashMap_get(properties->map, key); + } + return entry; } void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { @@ -649,7 +644,7 @@ void celix_properties_unset(celix_properties_t *properties, const char *key) { long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue) { long result = defaultValue; - celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { return entry->typed.longValue; } else if (entry != NULL) { @@ -669,7 +664,7 @@ void celix_properties_setLong(celix_properties_t *props, const char *key, long v double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { double result = defaultValue; - celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { return entry->typed.doubleValue; } else if (entry != NULL) { @@ -689,16 +684,16 @@ void celix_properties_setDouble(celix_properties_t *props, const char *key, doub bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { bool result = defaultValue; - celix_properties_entry_t* entry = celix_stringHashMap_get(props->map, key); + celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { return entry->typed.boolValue; } else if (entry != NULL) { char buf[32]; snprintf(buf, 32, "%s", entry->value); - char *trimmed = utils_stringTrim(buf); - if (strncasecmp("true", trimmed, strlen("true")) == 0) { + celix_utils_trimInPlace(buf); + if (strncasecmp("true", buf, strlen("true")) == 0) { result = true; - } else if (strncasecmp("false", trimmed, strlen("false")) == 0) { + } else if (strncasecmp("false", buf, strlen("false")) == 0) { result = false; } } @@ -713,13 +708,30 @@ const celix_version_t* celix_properties_getVersion( const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue) { - celix_properties_entry_t* entry = celix_stringHashMap_get(properties->map, key); + celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { return entry->typed.versionValue; } return defaultValue; } +celix_version_t* celix_properties_getAsVersion( + const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue) { + celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + return celix_version_copy(entry->typed.versionValue); + } + if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { + celix_version_t* createdVersion = celix_version_createVersionFromString(entry->value); + if (createdVersion != NULL) { + return createdVersion; + } + } + return defaultValue == NULL ? NULL : celix_version_copy(defaultValue); +} + void celix_properties_setVersion(celix_properties_t *properties, const char *key, const celix_version_t* version) { celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); } @@ -786,10 +798,10 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro celix_properties_iterator_t iter; iter.index = 0; - if (!celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { - memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); - } else { + if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { memset(&iter.entry, 0, sizeof(iter.entry)); + } else { + memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); } memset(&iter._data, 0, sizeof(iter._data)); @@ -814,6 +826,7 @@ void celix_propertiesIterator_next(celix_properties_iterator_t *iter) { memcpy(&internalIter, iter->_data, sizeof(internalIter)); celix_stringHashMapIterator_next(&internalIter.mapIter); memcpy(iter->_data, &internalIter, sizeof(internalIter)); + iter->index = internalIter.mapIter.index; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { memset(&iter->entry, 0, sizeof(iter->entry)); } else { @@ -829,11 +842,10 @@ bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter) { bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b) { celix_properties_iterator_internal_t internalIterA; - memcpy(&internalIterA, a, sizeof(internalIterA)); + memcpy(&internalIterA, a->_data, sizeof(internalIterA)); celix_properties_iterator_internal_t internalIterB; - memcpy(&internalIterB, b, sizeof(internalIterB)); - return internalIterA.props == internalIterB.props && - internalIterA.mapIter.key == internalIterB.mapIter.key; + memcpy(&internalIterB, b->_data, sizeof(internalIterB)); + return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); } celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter) { From ced3279168aae20863106951921b3b8e02ecea28 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 14:58:10 +0100 Subject: [PATCH 08/63] Refactor and improve properties and string hash map test suite and impl --- libs/framework/include/celix/Exception.h | 4 + libs/utils/gtest/CMakeLists.txt | 1 + .../utils/gtest/src/CxxPropertiesTestSuite.cc | 34 ++- libs/utils/gtest/src/CxxVersionTestSuite.cc | 93 +++++++ libs/utils/gtest/src/HashMapTestSuite.cc | 20 ++ libs/utils/gtest/src/PropertiesTestSuite.cc | 110 ++++---- libs/utils/gtest/src/VersionTestSuite.cc | 244 +++++++----------- libs/utils/include/celix/IOException.h | 51 ++++ libs/utils/include/celix/Properties.h | 70 ++++- libs/utils/include/celix/Version.h | 9 +- libs/utils/include/celix_long_hash_map.h | 8 + libs/utils/include/celix_properties.h | 14 +- libs/utils/include/celix_string_hash_map.h | 33 ++- libs/utils/src/celix_hash_map.c | 48 +++- libs/utils/src/properties.c | 51 ++-- libs/utils/src/version.c | 2 +- 16 files changed, 523 insertions(+), 269 deletions(-) create mode 100644 libs/utils/gtest/src/CxxVersionTestSuite.cc create mode 100644 libs/utils/include/celix/IOException.h diff --git a/libs/framework/include/celix/Exception.h b/libs/framework/include/celix/Exception.h index 1675567d4..b7186634c 100644 --- a/libs/framework/include/celix/Exception.h +++ b/libs/framework/include/celix/Exception.h @@ -27,7 +27,11 @@ namespace celix { */ class Exception : public std::exception { public: +#if __cplusplus >= 201703L //C++17 or higher + explicit Exception(std::string_view msg) : w{msg} {} +#else explicit Exception(std::string msg) : w{std::move(msg)} {} +#endif Exception(const Exception&) = default; Exception(Exception&&) = default; diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 1e512fe61..894fba324 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -32,6 +32,7 @@ add_executable(test_utils src/FileUtilsTestSuite.cc src/PropertiesTestSuite.cc src/VersionTestSuite.cc + src/CxxVersionTestSuite.cc ${CELIX_UTIL_TEST_SOURCES_FOR_CXX_HEADERS} ) diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index 142570c4b..b2f39192a 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -27,12 +27,12 @@ class CxxPropertiesTestSuite : public ::testing::Test { public: }; -TEST_F(CxxPropertiesTestSuite, testCreateDestroy) { +TEST_F(CxxPropertiesTestSuite, CreateDestroyTest) { celix::Properties props{}; EXPECT_EQ(0, props.size()); } -TEST_F(CxxPropertiesTestSuite, testFillAndLoop) { +TEST_F(CxxPropertiesTestSuite, FillAndLoopTest) { celix::Properties props{}; EXPECT_EQ(0, props.size()); @@ -66,7 +66,7 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) { EXPECT_EQ(5, count); } -TEST_F(CxxPropertiesTestSuite, testLoopForSize0And1) { +TEST_F(CxxPropertiesTestSuite, LoopForSize0And1Test) { celix::Properties props0{}; for (const auto& pair : props0) { FAIL() << "Should not get an loop entry with a properties size of 0. got key: " << pair.first; @@ -82,7 +82,7 @@ TEST_F(CxxPropertiesTestSuite, testLoopForSize0And1) { EXPECT_EQ(1, count); } -TEST_F(CxxPropertiesTestSuite, testCopy) { +TEST_F(CxxPropertiesTestSuite, CopyTest) { celix::Properties props{}; props["key1"] = "value1"; @@ -97,7 +97,7 @@ TEST_F(CxxPropertiesTestSuite, testCopy) { EXPECT_EQ(v2, "value1_new"); } -TEST_F(CxxPropertiesTestSuite, testWrap) { +TEST_F(CxxPropertiesTestSuite, WrapTest) { auto *props = celix_properties_create(); celix_properties_set(props, "test", "test"); @@ -112,7 +112,7 @@ TEST_F(CxxPropertiesTestSuite, testWrap) { celix_properties_destroy(props); } -TEST_F(CxxPropertiesTestSuite, getType) { +TEST_F(CxxPropertiesTestSuite, GetTypeTest) { celix::Properties props{}; props.set("bool", true); @@ -140,7 +140,7 @@ TEST_F(CxxPropertiesTestSuite, getType) { EXPECT_EQ(props.getType("version"), celix::Properties::ValueType::Version); } -TEST_F(CxxPropertiesTestSuite, testGetAsVersion) { +TEST_F(CxxPropertiesTestSuite, GetAsVersionTest) { celix::Properties props; // Test getting a version from a string property @@ -163,8 +163,24 @@ TEST_F(CxxPropertiesTestSuite, testGetAsVersion) { EXPECT_EQ(props.getAsVersion("key", celix::Version{4, 5, 6}), ver); } +TEST_F(CxxPropertiesTestSuite, StoreAndLoadTest) { + std::string path{"cxx_store_and_load_test.properties"}; + + celix::Properties props{}; + props.set("key1", 1); + props.set("key2", 2); + + EXPECT_NO_THROW(props.store(path)); + + celix::Properties loadedProps{}; + EXPECT_NO_THROW(loadedProps = celix::Properties::load(path)); + EXPECT_EQ(props.size(), loadedProps.size()); + + EXPECT_THROW(loadedProps = celix::Properties::load("non-existence"), celix::IOException); +} + #if __cplusplus >= 201703L //C++17 or higher -TEST_F(CxxPropertiesTestSuite, testStringView) { +TEST_F(CxxPropertiesTestSuite, StringViewTest) { constexpr std::string_view stringViewKey = "KEY1"; constexpr std::string_view stringViewValue = "VALUE1"; std::string stringKey{"KEY2"}; @@ -219,7 +235,7 @@ TEST_F(CxxPropertiesTestSuite, testStringView) { } } -TEST_F(CxxPropertiesTestSuite, testUseOfConstexprInSetMethod) { +TEST_F(CxxPropertiesTestSuite, UseOfConstexprInSetMethodTest) { celix::Properties props{}; //Test if different bool "types" are correctly handled diff --git a/libs/utils/gtest/src/CxxVersionTestSuite.cc b/libs/utils/gtest/src/CxxVersionTestSuite.cc new file mode 100644 index 000000000..0055d5c4a --- /dev/null +++ b/libs/utils/gtest/src/CxxVersionTestSuite.cc @@ -0,0 +1,93 @@ +/* + * 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 + +#include "celix/Version.h" + +class CxxVersionTestSuite : public ::testing::Test {}; + +#include "gtest/gtest.h" +#include "celix/Version.h" + +TEST_F(CxxVersionTestSuite, DefaultConstructorTest) { + celix::Version v; + EXPECT_EQ(0, v.getMajor()); + EXPECT_EQ(0, v.getMinor()); + EXPECT_EQ(0, v.getMicro()); + EXPECT_EQ("", v.getQualifier()); +} + +TEST_F(CxxVersionTestSuite, ConstructorTest) { + celix::Version v1{1, 2, 3, "qualifier"}; + EXPECT_EQ(1, v1.getMajor()); + EXPECT_EQ(2, v1.getMinor()); + EXPECT_EQ(3, v1.getMicro()); + EXPECT_EQ("qualifier", v1.getQualifier()); + + #if __cplusplus >= 201703L //C++17 or higher + celix::Version v2{1, 2, 3, std::string_view{"qualifier"}}; + EXPECT_EQ(1, v2.getMajor()); + EXPECT_EQ(2, v2.getMinor()); + EXPECT_EQ(3, v2.getMicro()); + EXPECT_EQ("qualifier", v2.getQualifier()); + #endif +} + +TEST_F(CxxVersionTestSuite, MoveConstructorTest) { + celix::Version v1{2, 3, 4, "qualifier"}; + celix::Version v2{std::move(v1)}; + + //v2 should have the values of v1 before the move + EXPECT_EQ(v2.getMajor(), 2); + EXPECT_EQ(v2.getMinor(), 3); + EXPECT_EQ(v2.getMicro(), 4); + EXPECT_EQ(v2.getQualifier(), "qualifier"); +} + +TEST_F(CxxVersionTestSuite, CopyConstructorTest) { + celix::Version v1{1, 2, 3, "qualifier"}; + celix::Version v2 = v1; + + EXPECT_EQ(v1, v2); + EXPECT_EQ(v1.getMajor(), v2.getMajor()); + EXPECT_EQ(v1.getMinor(), v2.getMinor()); + EXPECT_EQ(v1.getMicro(), v2.getMicro()); + EXPECT_EQ(v1.getQualifier(), v2.getQualifier()); +} + +TEST_F(CxxVersionTestSuite, MapKeyTest) { + // Test using Version as key in std::map + std::map map; + map[celix::Version{1, 2, 3}] = 1; + map[celix::Version{3, 2, 1}] = 2; + + EXPECT_EQ(map[(celix::Version{1, 2, 3})], 1); + EXPECT_EQ(map[(celix::Version{3, 2, 1})], 2); +} + +TEST_F(CxxVersionTestSuite, UnorderedMapKeyTest) { + // Test using Version as key in std::unordered_map + std::unordered_map unorderedMap; + unorderedMap[celix::Version{1, 2, 3}] = 1; + unorderedMap[celix::Version{3, 2, 1}] = 2; + + EXPECT_EQ(unorderedMap[(celix::Version{1, 2, 3})], 1); + EXPECT_EQ(unorderedMap[(celix::Version{3, 2, 1})], 2); +} diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 27f0589af..7f9010e3a 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -632,3 +632,23 @@ TEST_F(HashMapTestSuite, EqualsZeroSizeMapTest) { celix_stringHashMap_destroy(sMap); celix_longHashMap_destroy(lMap); } + +TEST_F(HashMapTestSuite, StoreKeysWeaklyTest) { + celix_string_hash_map_create_options_t opts{}; + opts.removedCallbackData = (void*)0x1; + opts.storeKeysWeakly = true; + opts.removedKeyCallback = [](void* data, char* key) { + EXPECT_EQ(data, (void*)0x1); + free(key); + }; + auto* sMap = celix_stringHashMap_createWithOptions(&opts); + EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); //new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key1", 2)); //replace key -> takes no ownership + + EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key2"), 3)); //new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key2", 4)); //replace key -> takes no ownership + celix_stringHashMap_remove(sMap, "key1"); + + celix_stringHashMap_destroy(sMap); +} + diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 74e96f6e7..f21b4049c 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -20,6 +20,7 @@ #include #include "celix_properties.h" +#include "celix_utils.h" using ::testing::MatchesRegex; @@ -28,14 +29,14 @@ class PropertiesTestSuite : public ::testing::Test { }; -TEST_F(PropertiesTestSuite, create) { +TEST_F(PropertiesTestSuite, CreateTest) { auto* properties = celix_properties_create(); EXPECT_TRUE(properties); celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, load) { +TEST_F(PropertiesTestSuite, LoadTest) { char propertiesFile[] = "resources-test/properties.txt"; auto* properties = celix_properties_load(propertiesFile); EXPECT_EQ(4, celix_properties_size(properties)); @@ -55,7 +56,21 @@ TEST_F(PropertiesTestSuite, load) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, asLong) { +TEST_F(PropertiesTestSuite, StoreTest) { + const char* propertiesFile = "resources-test/properties_out.txt"; + auto* properties = celix_properties_create(); + char keyA[] = "x"; + char keyB[] = "y"; + char valueA[] = "1"; + char valueB[] = "2"; + celix_properties_set(properties, keyA, valueA); + celix_properties_set(properties, keyB, valueB); + celix_properties_store(properties, propertiesFile, nullptr); + + celix_properties_destroy(properties); +} + +TEST_F(PropertiesTestSuite, GetAsLongTest) { celix_properties_t *props = celix_properties_create(); celix_properties_set(props, "t1", "42"); celix_properties_set(props, "t2", "-42"); @@ -84,21 +99,7 @@ TEST_F(PropertiesTestSuite, asLong) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, store) { - char propertiesFile[] = "resources-test/properties_out.txt"; - auto* properties = celix_properties_create(); - char keyA[] = "x"; - char keyB[] = "y"; - char valueA[] = "1"; - char valueB[] = "2"; - celix_properties_set(properties, keyA, valueA); - celix_properties_set(properties, keyB, valueB); - celix_properties_store(properties, propertiesFile, nullptr); - - celix_properties_destroy(properties); -} - -TEST_F(PropertiesTestSuite, copy) { +TEST_F(PropertiesTestSuite, CopyTest) { char propertiesFile[] = "resources-test/properties.txt"; auto* properties = celix_properties_load(propertiesFile); EXPECT_EQ(4, celix_properties_size(properties)); @@ -115,7 +116,7 @@ TEST_F(PropertiesTestSuite, copy) { celix_properties_destroy(copy); } -TEST_F(PropertiesTestSuite, getSet) { +TEST_F(PropertiesTestSuite, GetSetTest) { auto* properties = celix_properties_create(); char keyA[] = "x"; char keyB[] = "y"; @@ -137,7 +138,7 @@ TEST_F(PropertiesTestSuite, getSet) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, getSetWithNULL) { +TEST_F(PropertiesTestSuite, GetSetWithNullTest) { auto* properties = celix_properties_create(); celix_properties_set(properties, nullptr, "value"); @@ -153,7 +154,7 @@ TEST_F(PropertiesTestSuite, getSetWithNULL) { } -TEST_F(PropertiesTestSuite, setUnset) { +TEST_F(PropertiesTestSuite, SetUnsetTest) { auto* properties = celix_properties_create(); char keyA[] = "x"; char *keyD = strndup("a", 1); @@ -171,7 +172,7 @@ TEST_F(PropertiesTestSuite, setUnset) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, longTest) { +TEST_F(PropertiesTestSuite, GetLongTest) { auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "2"); @@ -201,7 +202,7 @@ TEST_F(PropertiesTestSuite, longTest) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, boolTest) { +TEST_F(PropertiesTestSuite, GetBoolTest) { auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "true"); @@ -233,7 +234,7 @@ TEST_F(PropertiesTestSuite, boolTest) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, fillTest) { +TEST_F(PropertiesTestSuite, GetFillTest) { celix_properties_t *props = celix_properties_create(); int testCount = 1000; for (int i = 0; i < 1000; ++i) { @@ -246,31 +247,34 @@ TEST_F(PropertiesTestSuite, fillTest) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, setOverwrite) { +TEST_F(PropertiesTestSuite, GetSetOverwrite) { auto* props = celix_properties_create(); auto* version = celix_version_createEmptyVersion(); - const char* key = "key"; - - celix_properties_set(props, key, "str1"); - EXPECT_STREQ("str1", celix_properties_get(props, key, "")); - celix_properties_set(props, key, "str2"); - EXPECT_STREQ("str2", celix_properties_get(props, key, "")); - celix_properties_setLong(props, key, 1); - EXPECT_EQ(1, celix_properties_getAsLong(props, key, -1L)); - celix_properties_setDouble(props, key, 2.0); - EXPECT_EQ(2.0, celix_properties_getAsLong(props, key, -2.0)); - celix_properties_setBool(props, key, false); - EXPECT_EQ(false, celix_properties_getAsBool(props, key, true)); - celix_properties_setVersionWithoutCopy(props, key, version); - EXPECT_EQ(version, celix_properties_getVersion(props, key, nullptr)); - celix_properties_set(props, key, "last"); + + { + char* key = celix_utils_strdup("key"); + celix_properties_set(props, key, "str1"); + free(key); + } + EXPECT_STREQ("str1", celix_properties_get(props, "key", "")); + celix_properties_set(props, "key", "str2"); + EXPECT_STREQ("str2", celix_properties_get(props, "key", "")); + celix_properties_setLong(props, "key", 1); + EXPECT_EQ(1, celix_properties_getAsLong(props, "key", -1L)); + celix_properties_setDouble(props, "key", 2.0); + EXPECT_EQ(2.0, celix_properties_getAsLong(props, "key", -2.0)); + celix_properties_setBool(props, "key", false); + EXPECT_EQ(false, celix_properties_getAsBool(props, "key", true)); + celix_properties_setVersionWithoutCopy(props, "key", version); + EXPECT_EQ(version, celix_properties_getVersion(props, "key", nullptr)); + celix_properties_set(props, "key", "last"); celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { +TEST_F(PropertiesTestSuite, SizeAndIteratorTest) { celix_properties_t *props = celix_properties_create(); EXPECT_EQ(0, celix_properties_size(props)); celix_properties_set(props, "a", "1"); @@ -291,7 +295,7 @@ TEST_F(PropertiesTestSuite, sizeAndIteratorTest) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, getType) { +TEST_F(PropertiesTestSuite, GetTypeTest) { auto* props = celix_properties_create(); celix_properties_set(props, "string", "value"); celix_properties_setLong(props, "long", 123); @@ -311,7 +315,7 @@ TEST_F(PropertiesTestSuite, getType) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, getEntry) { +TEST_F(PropertiesTestSuite, GetEntryTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_setLong(props, "key2", 123); @@ -354,7 +358,7 @@ TEST_F(PropertiesTestSuite, getEntry) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, iteratorNextKey) { +TEST_F(PropertiesTestSuite, IteratorNextKeyTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); @@ -374,7 +378,7 @@ TEST_F(PropertiesTestSuite, iteratorNextKey) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, iteratorNext) { +TEST_F(PropertiesTestSuite, IteratorNextTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); @@ -383,7 +387,7 @@ TEST_F(PropertiesTestSuite, iteratorNext) { int count = 0; auto iter = celix_properties_begin(props); while (!celix_propertiesIterator_isEnd(&iter)) { - EXPECT_NE(strstr(iter.entry.key, "key"), nullptr); + EXPECT_NE(strstr(iter.key, "key"), nullptr); EXPECT_NE(strstr(iter.entry.value, "value"), nullptr); count++; celix_propertiesIterator_next(&iter); @@ -395,7 +399,7 @@ TEST_F(PropertiesTestSuite, iteratorNext) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, iterateOverProperties) { +TEST_F(PropertiesTestSuite, IterateOverPropertiesTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); @@ -404,12 +408,12 @@ TEST_F(PropertiesTestSuite, iterateOverProperties) { int innerCount = 0; CELIX_PROPERTIES_ITERATE(props, outerIter) { outerCount++; - EXPECT_NE(strstr(outerIter.entry.key, "key"), nullptr); + EXPECT_NE(strstr(outerIter.key, "key"), nullptr); // Inner loop to test nested iteration CELIX_PROPERTIES_ITERATE(props, innerIter) { innerCount++; - EXPECT_NE(strstr(innerIter.entry.key, "key"), nullptr); + EXPECT_NE(strstr(innerIter.key, "key"), nullptr); } } // Check that both entries were iterated over @@ -428,7 +432,7 @@ TEST_F(PropertiesTestSuite, iterateOverProperties) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, getVersion) { +TEST_F(PropertiesTestSuite, GetVersionTest) { auto* properties = celix_properties_create(); auto* emptyVersion = celix_version_createEmptyVersion(); @@ -483,7 +487,7 @@ TEST_F(PropertiesTestSuite, getVersion) { celix_properties_destroy(properties); } -TEST_F(PropertiesTestSuite, TestEndOfProperties) { +TEST_F(PropertiesTestSuite, EndOfPropertiesTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); @@ -495,7 +499,7 @@ TEST_F(PropertiesTestSuite, TestEndOfProperties) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, TestEndOfEmptyProperties) { +TEST_F(PropertiesTestSuite, EndOfEmptyPropertiesTest) { auto* props = celix_properties_create(); celix_properties_iterator_t endIter = celix_properties_end(props); @@ -504,3 +508,5 @@ TEST_F(PropertiesTestSuite, TestEndOfEmptyProperties) { celix_properties_destroy(props); } + +//TODO test replace and replace WithCopy \ No newline at end of file diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc index 90e5e9f1c..54539c7b9 100644 --- a/libs/utils/gtest/src/VersionTestSuite.cc +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -24,208 +24,134 @@ #include "celix_utils.h" #include "version.h" -extern "C" -{ -#include "version_private.h" -} - -class VersionTestSuite : public ::testing::Test {}; - -TEST_F(VersionTestSuite, create) { +class VersionTestSuite : public ::testing::Test { +public: + void expectVersion(const celix_version_t* version, int major, int minor, int micro, const char* qualifier = "") { + if (version) { + EXPECT_EQ(major, celix_version_getMajor(version)); + EXPECT_EQ(minor, celix_version_getMinor(version)); + EXPECT_EQ(micro, celix_version_getMicro(version)); + EXPECT_STREQ(qualifier, celix_version_getQualifier(version)); + } + } +}; + +TEST_F(VersionTestSuite, CreateTest) { celix_version_t* version = nullptr; - char * str; - str = celix_utils_strdup("abc"); - EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, str, &version)); + version = celix_version_create(1, 2, 3, "abc"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - EXPECT_STREQ("abc", version->qualifier); + expectVersion(version, 1, 2, 3, "abc"); + celix_version_destroy(version); - version_destroy(version); + EXPECT_EQ(nullptr, celix_version_createVersion(-1, -2, -3, "abc")); + EXPECT_EQ(nullptr, celix_version_createVersion(-1, -2, -3, "abc|xyz")); + + + //Testing deprecated api version = nullptr; EXPECT_EQ(CELIX_SUCCESS, version_createVersion(1, 2, 3, nullptr, &version)); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - EXPECT_STREQ("", version->qualifier); - + int major; + int minor; + int micro; + const char* q; + EXPECT_EQ(CELIX_SUCCESS, version_getMajor(version, &major)); + EXPECT_EQ(CELIX_SUCCESS, version_getMinor(version, &minor)); + EXPECT_EQ(CELIX_SUCCESS, version_getMicro(version, µ)); + EXPECT_EQ(CELIX_SUCCESS, version_getQualifier(version, &q)); + EXPECT_EQ(1, major); + EXPECT_EQ(2, minor); + EXPECT_EQ(3, micro); + EXPECT_STREQ("", q); version_destroy(version); - version = nullptr; - free(str); - str = celix_utils_strdup("abc"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(-1, -2, -3, str, &version)); - version_destroy(version); version = nullptr; - free(str); - str = celix_utils_strdup("abc|xyz"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(1, 2, 3, str, &version)); - - version_destroy(version); - free(str); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(-1, -2, -3, "abc", &version)); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersion(1, 2, 3, "abc|xyz", &version)); } -TEST_F(VersionTestSuite, copy) { +TEST_F(VersionTestSuite, CopyTest) { auto* version = celix_version_create(1, 2, 3, "abc"); auto* copy = celix_version_copy(version); EXPECT_NE(nullptr, version); EXPECT_NE(nullptr, copy); - EXPECT_EQ(1, celix_version_getMajor(copy)); - EXPECT_EQ(2, celix_version_getMinor(copy)); - EXPECT_EQ(3, celix_version_getMicro(copy)); - EXPECT_STREQ("abc", celix_version_getQualifier(copy)); + expectVersion(version, 1, 2, 3, "abc"); celix_version_destroy(copy); celix_version_destroy(version); copy = celix_version_copy(nullptr); //returns "empty" version EXPECT_NE(nullptr, copy); - EXPECT_EQ(0, celix_version_getMajor(copy)); - EXPECT_EQ(0, celix_version_getMinor(copy)); - EXPECT_EQ(0, celix_version_getMicro(copy)); - EXPECT_STREQ("", celix_version_getQualifier(copy)); + expectVersion(copy, 0, 0, 0, ""); celix_version_destroy(copy); } -TEST_F(VersionTestSuite, createFromString) { - celix_version_t* version = nullptr; - celix_status_t status = CELIX_SUCCESS; - char * str; - - str = celix_utils_strdup("1"); - EXPECT_EQ(CELIX_SUCCESS, version_createVersionFromString(str, &version)); +TEST_F(VersionTestSuite, CreateFromStringTest) { + auto* version = celix_version_createVersionFromString("1"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - + expectVersion(version, 1, 0, 0); version_destroy(version); - free(str); - str = celix_utils_strdup("a"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = celix_utils_strdup("1.a"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = celix_utils_strdup("1.1.a"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); - - free(str); - str = celix_utils_strdup("-1"); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString(str, &version)); + EXPECT_EQ(nullptr, celix_version_createVersionFromString("a")); + EXPECT_EQ(nullptr, celix_version_createVersionFromString("1.a")); + EXPECT_EQ(nullptr, celix_version_createVersionFromString("1.1.a")); + EXPECT_EQ(nullptr, celix_version_createVersionFromString("-1")); + EXPECT_EQ(nullptr, celix_version_createVersionFromString("1.2.3.abc|xyz")); - free(str); - str = celix_utils_strdup("1.2"); - version = nullptr; - EXPECT_EQ(CELIX_SUCCESS, version_createVersionFromString(str, &version)); + version = celix_version_createVersionFromString("1.2"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - + expectVersion(version, 1, 2, 0); version_destroy(version); - free(str); - str = celix_utils_strdup("1.2.3"); - version = nullptr; - status = version_createVersionFromString(str, &version); - EXPECT_EQ(CELIX_SUCCESS, status); + version = celix_version_createVersionFromString("1.2.3"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - + expectVersion(version, 1, 2, 3); version_destroy(version); - free(str); - str = celix_utils_strdup("1.2.3.abc"); - version = nullptr; - status = version_createVersionFromString(str, &version); - EXPECT_EQ(CELIX_SUCCESS, status); - EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - EXPECT_STREQ("abc", version->qualifier); - version_destroy(version); - free(str); - str = celix_utils_strdup("1.2.3.abc_xyz"); - version = nullptr; - status = version_createVersionFromString(str, &version); - EXPECT_EQ(CELIX_SUCCESS, status); + version = celix_version_createVersionFromString("1.2.3.abc"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - EXPECT_STREQ("abc_xyz", version->qualifier); - + expectVersion(version, 1, 2, 3, "abc"); version_destroy(version); - free(str); - str = celix_utils_strdup("1.2.3.abc-xyz"); - version = nullptr; - status = version_createVersionFromString(str, &version); - EXPECT_EQ(CELIX_SUCCESS, status); - EXPECT_TRUE(version != nullptr); - EXPECT_EQ(1, version->major); - EXPECT_EQ(2, version->minor); - EXPECT_EQ(3, version->micro); - EXPECT_STREQ("abc-xyz", version->qualifier); + version = celix_version_createVersionFromString("1.2.3.abc_xyz"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3, "abc_xyz"); version_destroy(version); - free(str); - str = celix_utils_strdup("1.2.3.abc|xyz"); - status = version_createVersionFromString(str, &version); - EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, status); - free(str); -} - -TEST_F(VersionTestSuite, createEmptyVersion) { - celix_version_t* version = nullptr; - celix_status_t status = CELIX_SUCCESS; - status = version_createEmptyVersion(&version); - EXPECT_EQ(CELIX_SUCCESS, status); + version = celix_version_createVersionFromString("1.2.3.abc-xyz"); EXPECT_TRUE(version != nullptr); - EXPECT_EQ(0, version->major); - EXPECT_EQ(0, version->minor); - EXPECT_EQ(0, version->micro); - EXPECT_STREQ("", version->qualifier); - + expectVersion(version, 1, 2, 3, "abc-xyz"); version_destroy(version); -} -TEST_F(VersionTestSuite, getters) { - celix_version_t* version = nullptr; - celix_status_t status = CELIX_SUCCESS; - char * str; - int major, minor, micro; - const char *qualifier; - str = celix_utils_strdup("abc"); - status = version_createVersion(1, 2, 3, str, &version); - EXPECT_EQ(CELIX_SUCCESS, status); + //Testing deprecated api + version = nullptr; + EXPECT_EQ(CELIX_SUCCESS, version_createVersionFromString("1", &version)); EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 0, 0); + version_destroy(version); - version_getMajor(version, &major); - EXPECT_EQ(1, major); - - version_getMinor(version, &minor); - EXPECT_EQ(2, minor); - - version_getMicro(version, µ); - EXPECT_EQ(3, micro); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString("a", &version)); +} - version_getQualifier(version, &qualifier); - EXPECT_STREQ("abc", qualifier); +TEST_F(VersionTestSuite, CreateEmptyVersionTest) { + auto* version = celix_version_createEmptyVersion(); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 0, 0, 0, ""); + version_destroy(version); + //Testing deprecated api + version = nullptr; + EXPECT_EQ(CELIX_SUCCESS, version_createEmptyVersion(&version)); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 0, 0, 0, ""); version_destroy(version); - free(str); } -TEST_F(VersionTestSuite, compare) { +TEST_F(VersionTestSuite, CompareTest) { + //TODO update to updated API + celix_version_t* version = nullptr; celix_version_t* compare = nullptr; celix_status_t status = CELIX_SUCCESS; @@ -261,7 +187,7 @@ TEST_F(VersionTestSuite, compare) { EXPECT_EQ(CELIX_SUCCESS, status); EXPECT_TRUE(result < 0); - // Compare againts a lower version + // Compare against a lower version free(str); str = celix_utils_strdup("abc"); version_destroy(compare); @@ -278,7 +204,7 @@ TEST_F(VersionTestSuite, compare) { free(str); } -TEST_F(VersionTestSuite, celix_version_compareToMajorMinor) { +TEST_F(VersionTestSuite, CompareToMajorMinorTest) { auto* version1 = celix_version_create(2, 2, 0, nullptr); auto* version2 = celix_version_create(2, 2, 4, "qualifier"); @@ -300,7 +226,9 @@ TEST_F(VersionTestSuite, celix_version_compareToMajorMinor) { celix_version_destroy(version2); } -TEST_F(VersionTestSuite, toString) { +TEST_F(VersionTestSuite, ToStringTest) { + //TODO update to updated API + celix_version_t* version = nullptr; celix_status_t status = CELIX_SUCCESS; char * str; @@ -333,7 +261,9 @@ TEST_F(VersionTestSuite, toString) { free(str); } -TEST_F(VersionTestSuite,semanticCompatibility) { +TEST_F(VersionTestSuite, SemanticCompatibilityTest) { + //TODO update to updated API + celix_version_t* provider = nullptr; celix_version_t* compatible_user = nullptr; celix_version_t* incompatible_user_by_major = nullptr; @@ -367,7 +297,7 @@ TEST_F(VersionTestSuite,semanticCompatibility) { version_destroy(incompatible_user_by_minor); } -TEST_F(VersionTestSuite, compareEmptyAndNullQualifier) { +TEST_F(VersionTestSuite, CompareEmptyAndNullQualifierTest) { //nullptr or "" qualifier should be the same auto* v1 = celix_version_create(0, 0, 0, nullptr); auto* v2 = celix_version_create(0, 0, 0, ""); @@ -379,7 +309,7 @@ TEST_F(VersionTestSuite, compareEmptyAndNullQualifier) { celix_version_destroy(v2); } -TEST_F(VersionTestSuite, fillString) { +TEST_F(VersionTestSuite, FillStringTest) { // Create a version object auto* version = celix_version_create(1, 2, 3, "alpha"); diff --git a/libs/utils/include/celix/IOException.h b/libs/utils/include/celix/IOException.h new file mode 100644 index 000000000..1715801e3 --- /dev/null +++ b/libs/utils/include/celix/IOException.h @@ -0,0 +1,51 @@ +/* + * 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. + */ +#pragma once + +#include +#if __cplusplus >= 201703L //C++17 or higher +#include +#endif + +namespace celix { + + /** + * @brief Celix runtime IO Exception + */ + class IOException : public std::exception { + public: +#if __cplusplus >= 201703L //C++17 or higher + explicit IOException(std::string_view msg) : w{msg} {} +#else + explicit IOException(std::string msg) : w{std::move(msg)} {} +#endif + + IOException(const IOException&) = default; + IOException(IOException&&) = default; + IOException& operator=(const IOException&) = default; + IOException& operator=(IOException&&) = default; + + [[nodiscard]] const char* what() const noexcept override { + return w.c_str(); + } + private: + std::string w; + }; + +} diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 45bb0c8be..5be5a7621 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -27,6 +27,7 @@ #include "celix_properties.h" #include "celix_utils.h" #include "celix/Version.h" +#include "celix/IOException.h" namespace celix { @@ -75,12 +76,12 @@ namespace celix { first = {}; second = {}; } else { - first = iter.entry.key; + first = iter.key; second = iter.entry.value; } } - celix_properties_iterator_t iter{.index = -1, .entry = {}, ._data = {}}; + celix_properties_iterator_t iter{.index = 0, .key = nullptr, .entry = {}, ._data = {}}; }; @@ -190,11 +191,19 @@ namespace celix { #endif /** - * @brief Wraps C properties, but does not take ownership -> dtor will not destroy properties + * @brief Wrap C properties and returns it as const in a shared_ptr, + * but does not take ownership -> dtor will not destroy C properties. */ static std::shared_ptr wrap(const celix_properties_t* wrapProps) { auto* cp = const_cast(wrapProps); - return std::shared_ptr{new Properties{cp}}; + return std::shared_ptr{new Properties{cp, false}}; + } + + /** + * @brief Wrap C properties and take ownership -> dtor will destroy C properties. + */ + static Properties own(celix_properties_t* wrapProps) { + return Properties{wrapProps, true}; } /** @@ -598,15 +607,43 @@ namespace celix { #endif - //TODO test - void store(const std::string& file, const std::string& header = {}) const { - celix_properties_store(cProps.get(), file.c_str(), header.empty() ? nullptr : header.c_str()); + + /** + * @brief Store the property set to the given file path. + * + * This function writes the properties in the given set to the specified file path in a format suitable + * for loading with the load() function. + * If a non-empty header string is provided, it will be written as a comment at the beginning of the file. + * + * @param[in] file The file to store the properties to. + * @param[in] header An optional header string to include as a comment at the beginning of the file. + * @throws celix::IOException If an error occurs while writing to the file. + */ +#if __cplusplus >= 201703L //C++17 or higher + void store(std::string_view path, std::string_view header = {}) const { + storeTo(path.data(), header.empty() ? nullptr : header.data()); + } +#else + void store(const std::string& path, const std::string& header = {}) const { + storeTo(path.data(), header.empty() ? nullptr : header.data()); } +#endif - //TODO laod + /** + * @brief Loads properties from the file at the given path. + * @param[in] path The path to the file containing the properties. + * @return A new Properties object containing the properties from the file. + * @throws celix::IOException If the file cannot be opened or read. + */ +#if __cplusplus >= 201703L //C++17 or higher + static celix::Properties load(std::string_view path) { return loadFrom(path.data()); } +#else + static celix::Properties load(const std::string& path) { return loadFrom(path.data()); } +#endif private: - explicit Properties(celix_properties_t* props) : cProps{props, [](celix_properties_t*) { /*nop*/ }} {} + Properties(celix_properties_t* props, bool takeOwnership) : + cProps{props, [ownership = takeOwnership](celix_properties_t* p){ if (ownership) { celix_properties_destroy(p); }}} {} static celix::Properties::ValueType getAndConvertType( const std::shared_ptr& cProperties, @@ -628,6 +665,21 @@ namespace celix { } } + static celix::Properties loadFrom(const char* path) { + auto* cProps = celix_properties_load(path); + if (cProps) { + return celix::Properties::own(cProps); + } + throw celix::IOException{"Cannot load celix::Properties from path " + std::string{path}}; + } + + void storeTo(const char* path, const char* header) const { + auto status = celix_properties_store(cProps.get(), path, header); + if (status != CELIX_SUCCESS) { + throw celix::IOException{"Cannot store celix::Properties to " + std::string{path}}; + } + } + std::shared_ptr cProps; }; diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h index ef50be0f9..c8d2943d4 100644 --- a/libs/utils/include/celix/Version.h +++ b/libs/utils/include/celix/Version.h @@ -26,10 +26,7 @@ namespace celix { - //TODO CxxVersionTestSuite //TODO doxygen - //TODO test in unordered map and set - //TODO test in map and set class Version { public: Version() : @@ -37,7 +34,7 @@ namespace celix { qualifier{celix_version_getQualifier(cVersion.get())} {} #if __cplusplus >= 201703L //C++17 or higher - explicit Version(int major, int minor, int micro, std::string_view qualifier = {}) : + Version(int major, int minor, int micro, std::string_view qualifier = {}) : cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))}, qualifier{celix_version_getQualifier(cVersion.get())} {} #else @@ -110,7 +107,9 @@ namespace celix { /** * @brief Create a wrap around a C Celix version without taking ownership. */ - explicit Version(celix_version_t* v) : cVersion{v, [](celix_version_t *){/*nop*/}} {} + explicit Version(celix_version_t* v) : + cVersion{v, [](celix_version_t *){/*nop*/}}, + qualifier{celix_version_getQualifier(cVersion.get())} {} std::shared_ptr cVersion; std::string qualifier; //cached qualifier of the const char* from celix_version_getQualifier diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index c0c36968c..aacfef589 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -69,6 +69,9 @@ typedef struct celix_long_hash_map_create_options { * only the simpleRemovedCallback will be used. * * Default is NULL. + * + * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the + * hash map and can be freed. */ void (*simpleRemovedCallback)(void* value) CELIX_OPTS_INIT; @@ -89,6 +92,11 @@ typedef struct celix_long_hash_map_create_options { * only the simpleRemovedCallback will be used. * * Default is NULL. + * + * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData. + * @param[in] removedKey The key of the value that was removed from the hash map. + * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the + * hash map and can be freed. */ void (*removedCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT; diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index f20a2fe85..71bc0f536 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -70,7 +70,6 @@ typedef enum celix_properties_value_type { * @brief A structure representing a single entry in a property set. */ typedef struct celix_properties_entry { - const char* key; /**< The key of the entry*/ const char* value; /**< The string value or string representation of a non-string typed value.*/ celix_properties_value_type_e valueType; /**< The type of the value of the entry */ @@ -94,10 +93,15 @@ typedef struct celix_properties_iterator { /** * @brief The index of the current iterator. */ - int index; + size_t index; /** - * @brief The current entry. + * @brief Te current key. + */ + const char* key; + + /** + * @brief The current value entry. */ celix_properties_entry_t entry; @@ -152,6 +156,8 @@ celix_properties_t* celix_properties_loadFromString(const char *input); /** * @brief Store properties to a file. * + * @note Properties values are always stored as string values, regardless of their actual underlining types. + * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. * @param[in] header An optional header to write to the file before the properties. @@ -200,6 +206,8 @@ void celix_properties_set(celix_properties_t* properties, const char* key, const /** * @brief Set the value of a property without copying the key and value strings. * + * @note If the setWithoutCopy replaced an exising value, the key will be freed by callee. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or modified * after calling this function. diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 323755cb6..7dfe661f7 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -68,11 +68,14 @@ typedef struct celix_string_hash_map_create_options { * only the simpledRemoveCallback will be used. * * Default is NULL. + * + * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the + * hash map and can be freed. */ - void (*simpleRemovedCallback)(void* value) CELIX_OPTS_INIT; + void (*simpleRemovedCallback)(void* removedValue) CELIX_OPTS_INIT; /** - * Optional callback data, which will be provided to the removedCallback callback. + * Optional callback data, which will be provided to the removedCallback and removedKeyCallback callback. * * Default is NULL. */ @@ -88,14 +91,37 @@ typedef struct celix_string_hash_map_create_options { * only the simpledRemoveCallback will be used. * * Default is NULL. + * + * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData. + * @param[in] removedKey The key of the value that was removed from the hash map. + * Note that the removedKey can still be in use if the a value entry for the same key is + * replaced, so this callback should not free the removedKey. + * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the + * hash map and can be freed. */ void (*removedCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT; + /** + * @brief A removed key callback, which if provided will be called if a key is no longer used in the hash map. + * + * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData. + * @param[in] key The key that is no longer used in the hash map. if `storeKeysWeakly` was configured as true, + * the key can be freed. + */ + void (*removedKeyCallback)(void* data, char* key) CELIX_OPTS_INIT; + /** * @brief If set to true, the string hash map will not make of copy of the keys and assumes * that the keys are in scope/memory for the complete lifecycle of the string hash map. * - * Note that this changes the default behaviour of the celix_stringHashMap_put* functions. + * When keys are stored weakly it is the caller responsibility to check the return value of + * celix_stringHashMap_put* function calls. + * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key + * should never be freed or freed in a configured removedKeyCallback. + * If a celix_stringHashMap_put* function call returns false, a value is replaced and the already existing + * key is reused. If the needed the caller should free the provided key. + * + * @note This changes the default behaviour of the celix_stringHashMap_put* functions. * * Default is false. */ @@ -137,6 +163,7 @@ typedef struct celix_string_hash_map_create_options { .simpleRemovedCallback = NULL, \ .removedCallbackData = NULL, \ .removedCallback = NULL, \ + .removedKeyCallback = NULL, \ .storeKeysWeakly = false, \ .initialCapacity = 0, \ .loadFactor = 0 \ diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 42e37ec62..995b0e6bb 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -73,8 +73,9 @@ typedef struct celix_hash_map { bool (*equalsKeyFunction)(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2); void (*simpleRemovedCallback)(void* value); void* removedCallbackData; - void (*removedStringKeyCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue); - void (*removedLongKeyCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue); + void (*removedStringEntryCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue); + void (*removedStringKeyCallback)(void* data, char* key); + void (*removedLongEntryCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue); bool storeKeysWeakly; } celix_hash_map_t; @@ -227,18 +228,24 @@ static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { static void celix_hashMap_callRemovedCallback(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { if (map->simpleRemovedCallback) { map->simpleRemovedCallback(removedEntry->value.ptrValue); - } else if (map->removedLongKeyCallback) { - map->removedLongKeyCallback(map->removedCallbackData, removedEntry->key.longKey, removedEntry->value); - } else if (map->removedStringKeyCallback) { - map->removedStringKeyCallback(map->removedCallbackData, removedEntry->key.strKey, removedEntry->value); + } else if (map->removedLongEntryCallback) { + map->removedLongEntryCallback(map->removedCallbackData, removedEntry->key.longKey, removedEntry->value); + } else if (map->removedStringEntryCallback) { + map->removedStringEntryCallback(map->removedCallbackData, removedEntry->key.strKey, removedEntry->value); + } +} + +static void celix_hashMap_destroyRemovedKey(celix_hash_map_t* map, char* removedKey) { + if (map->removedStringKeyCallback) { + map->removedStringKeyCallback(map->removedCallbackData, removedKey); + } + if (!map->storeKeysWeakly) { + free(removedKey); } } static void celix_hashMap_destroyRemovedEntry(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { celix_hashMap_callRemovedCallback(map, removedEntry); - if (map->keyType == CELIX_HASH_MAP_STRING_KEY && !map->storeKeysWeakly) { - free((char*)removedEntry->key.strKey); - } free(removedEntry); } @@ -344,7 +351,14 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long visit = visit->next; } if (removedEntry != NULL) { + char* removedKey = NULL; + if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { + removedKey = (char*)removedEntry->key.strKey; + } celix_hashMap_destroyRemovedEntry(map, removedEntry); + if (removedKey) { + celix_hashMap_destroyRemovedKey(map, removedKey); + } return true; } return false; @@ -367,7 +381,8 @@ static void celix_hashMap_init( map->equalsKeyFunction = equalsKeyFn; map->simpleRemovedCallback = NULL; map->removedCallbackData = NULL; - map->removedLongKeyCallback = NULL; + map->removedLongEntryCallback = NULL; + map->removedStringEntryCallback = NULL; map->removedStringKeyCallback = NULL; map->storeKeysWeakly = false; } @@ -378,7 +393,15 @@ static void celix_hashMap_clear(celix_hash_map_t* map) { while (entry != NULL) { celix_hash_map_entry_t* removedEntry = entry; entry = entry->next; + \ + char* removedKey = NULL; + if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { + removedKey = (char*)removedEntry->key.strKey; + } celix_hashMap_destroyRemovedEntry(map, removedEntry); + if (removedKey) { + celix_hashMap_destroyRemovedKey(map, removedKey); + } } map->buckets[i] = NULL; } @@ -427,7 +450,8 @@ celix_string_hash_map_t* celix_stringHashMap_createWithOptions(const celix_strin celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; map->genericMap.removedCallbackData = opts->removedCallbackData; - map->genericMap.removedStringKeyCallback = opts->removedCallback; + map->genericMap.removedStringEntryCallback = opts->removedCallback; + map->genericMap.removedStringKeyCallback = opts->removedKeyCallback; map->genericMap.storeKeysWeakly = opts->storeKeysWeakly; return map; } @@ -444,7 +468,7 @@ celix_long_hash_map_t* celix_longHashMap_createWithOptions(const celix_long_hash celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_equals); map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; map->genericMap.removedCallbackData = opts->removedCallbackData; - map->genericMap.removedLongKeyCallback = opts->removedCallback; + map->genericMap.removedLongEntryCallback = opts->removedCallback; map->genericMap.storeKeysWeakly = false; return map; } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 422a2bb70..dbbc20481 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -160,14 +160,12 @@ static char* celix_properties_createString(celix_properties_t* properties, const static celix_status_t celix_properties_fillEntry( celix_properties_t *properties, celix_properties_entry_t* entry, - const char *key, const char *strValue, const long* longValue, const double* doubleValue, const bool* boolValue, celix_version_t* versionValue) { char convertedValueBuffer[32]; - entry->key = celix_properties_createString(properties, key); if (strValue != NULL) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; entry->value = celix_properties_createString(properties, strValue); @@ -209,7 +207,7 @@ static celix_status_t celix_properties_fillEntry( entry->value = celix_version_toString(versionValue); } } - if (entry->key == NULL || entry->value == NULL) { + if (entry->value == NULL) { return CELIX_ENOMEM; } return CELIX_SUCCESS; @@ -233,13 +231,11 @@ static celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* * provided key and value strings. */ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_properties_t *properties, - char *key, char *strValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { return NULL; } - entry->key = key; entry->value = strValue; entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; entry->typed.strValue = strValue; @@ -264,7 +260,7 @@ static celix_properties_entry_t* celix_properties_createEntry( return NULL; } - celix_status_t status = celix_properties_fillEntry(properties, entry, key, strValue, longValue, doubleValue, + celix_status_t status = celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); if (status != CELIX_SUCCESS) { free(entry); @@ -290,8 +286,13 @@ static void celix_properties_createAndSetEntry( } celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, boolValue, versionValue); + const char* mapKey = key; + if (!celix_stringHashMap_hasKey(properties->map, key)) { + //new entry, needs new allocated key; + mapKey = celix_properties_createString(properties, key); + } if (entry != NULL) { - celix_stringHashMap_put(properties->map, entry->key, entry); + celix_stringHashMap_put(properties->map, mapKey, entry); } } @@ -308,10 +309,14 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st } } +static void celix_properties_removeKeyCallback(void* handle, char* key) { + celix_properties_t* properties = handle; + celix_properties_freeString(properties, key); +} + static void celix_properties_removeEntryCallback(void* handle, const char* key __attribute__((unused)), celix_hash_map_value_t val) { celix_properties_t* properties = handle; celix_properties_entry_t* entry = val.ptrValue; - celix_properties_freeString(properties, (char*)entry->key); celix_properties_freeString(properties, (char*)entry->value); if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { celix_version_destroy(entry->typed.versionValue); @@ -332,6 +337,7 @@ celix_properties_t* celix_properties_create(void) { opts.initialCapacity = (unsigned int)ceil(CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE / 0.75); opts.removedCallbackData = props; opts.removedCallback = celix_properties_removeEntryCallback; + opts.removedKeyCallback = celix_properties_removeKeyCallback; props->map = celix_stringHashMap_createWithOptions(&opts); props->currentStringBufferIndex = 0; props->currentEntriesBufferIndex = 0; @@ -585,16 +591,16 @@ celix_properties_t* celix_properties_copy(const celix_properties_t *properties) CELIX_PROPERTIES_ITERATE(properties, iter) { if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { - celix_properties_set(copy, iter.entry.key, iter.entry.value); + celix_properties_set(copy, iter.key, iter.entry.value); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { - celix_properties_setLong(copy, iter.entry.key, iter.entry.typed.longValue); + celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { - celix_properties_setDouble(copy, iter.entry.key, iter.entry.typed.doubleValue); + celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { - celix_properties_setBool(copy, iter.entry.key, iter.entry.typed.boolValue); + celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue); } else /*version*/ { assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION); - celix_properties_setVersion(copy, iter.entry.key, iter.entry.typed.versionValue); + celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue); } } return copy; @@ -629,9 +635,12 @@ void celix_properties_set(celix_properties_t *properties, const char *key, const void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { if (properties != NULL && key != NULL && value != NULL) { - celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, key, value); + celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value); if (entry != NULL) { - celix_stringHashMap_put(properties->map, entry->key, entry); + bool replaced = celix_stringHashMap_put(properties->map, key, entry); + if (replaced) { + free(key); + } } } } @@ -755,7 +764,8 @@ celix_properties_iterator_t celix_propertiesIterator_construct(const celix_prope internalIter.props = properties; celix_properties_iterator_t iter; - iter.index = -1; + iter.index = 0; + iter.key = NULL; memset(&iter.entry, 0, sizeof(iter.entry)); CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(iter._data)); @@ -767,7 +777,6 @@ celix_properties_iterator_t celix_propertiesIterator_construct(const celix_prope bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) { celix_properties_iterator_internal_t internalIter; memcpy(&internalIter, iter->_data, sizeof(internalIter)); - //celix_stringHashMapIterator_next(&internalIter.mapIter); return !celix_stringHashMapIterator_isEnd(&internalIter.mapIter); } @@ -780,8 +789,10 @@ const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) iter->index = (int)internalIter.mapIter.index; celix_properties_entry_t* entry = internalIter.mapIter.value.ptrValue; if (entry != NULL) { + iter->key = internalIter.mapIter.key; memcpy(&iter->entry, entry, sizeof(iter->entry)); } else { + iter->key = NULL; memset(&iter->entry, 0, sizeof(iter->entry)); } celix_stringHashMapIterator_next(&internalIter.mapIter); @@ -799,8 +810,10 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro celix_properties_iterator_t iter; iter.index = 0; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { + iter.key = NULL; memset(&iter.entry, 0, sizeof(iter.entry)); } else { + iter.key = internalIter.mapIter.key; memcpy(&iter.entry, internalIter.mapIter.value.ptrValue, sizeof(iter.entry)); } @@ -816,7 +829,7 @@ celix_properties_iterator_t celix_properties_end(const celix_properties_t* prope celix_properties_iterator_t iter; memset(&iter, 0, sizeof(iter)); - iter.index = (int)internalIter.mapIter.index; //TODO make inter.index size_t + iter.index = internalIter.mapIter.index; memcpy(iter._data, &internalIter, sizeof(internalIter)); return iter; } @@ -828,8 +841,10 @@ void celix_propertiesIterator_next(celix_properties_iterator_t *iter) { memcpy(iter->_data, &internalIter, sizeof(internalIter)); iter->index = internalIter.mapIter.index; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { + iter->key = NULL; memset(&iter->entry, 0, sizeof(iter->entry)); } else { + iter->key = internalIter.mapIter.key; memcpy(&iter->entry, internalIter.mapIter.value.ptrValue, sizeof(iter->entry)); } } diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index 72a379426..b44b123f2 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -107,7 +107,7 @@ celix_version_t* celix_version_create(int major, int minor, int micro, const cha } if (qualifier == NULL) { - qualifier = ""; + qualifier = CELIX_VERSION_EMPTY_QUALIFIER; } size_t qualifierLen = strlen(qualifier); for (int i = 0; i < qualifierLen; i++) { From 71daacf3d0dda6f16817cae707a302b3eab9ce8a Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 14:59:04 +0100 Subject: [PATCH 09/63] Remove old properties.c file --- libs/utils/src/properties_v1.c | 513 --------------------------------- 1 file changed, 513 deletions(-) delete mode 100644 libs/utils/src/properties_v1.c diff --git a/libs/utils/src/properties_v1.c b/libs/utils/src/properties_v1.c deleted file mode 100644 index 3d962b02e..000000000 --- a/libs/utils/src/properties_v1.c +++ /dev/null @@ -1,513 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include "properties.h" -#include "celix_build_assert.h" -#include "celix_properties.h" -#include "utils.h" -#include "hash_map_private.h" -#include -#include "hash_map.h" - -#define MALLOC_BLOCK_SIZE 5 - -static void parseLine(const char* line, celix_properties_t *props); - -properties_pt properties_create(void) { - return celix_properties_create(); -} - -void properties_destroy(properties_pt properties) { - celix_properties_destroy(properties); -} - -properties_pt properties_load(const char* filename) { - return celix_properties_load(filename); -} - -properties_pt properties_loadWithStream(FILE *file) { - return celix_properties_loadWithStream(file); -} - -properties_pt properties_loadFromString(const char *input){ - return celix_properties_loadFromString(input); -} - - -/** - * Header is ignored for now, cannot handle comments yet - */ -void properties_store(properties_pt properties, const char* filename, const char* header) { - return celix_properties_store(properties, filename, header); -} - -celix_status_t properties_copy(properties_pt properties, properties_pt *out) { - celix_properties_t *copy = celix_properties_copy(properties); - *out = copy; - return copy == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS; -} - -const char* properties_get(properties_pt properties, const char* key) { - return celix_properties_get(properties, key, NULL); -} - -const char* properties_getWithDefault(properties_pt properties, const char* key, const char* defaultValue) { - return celix_properties_get(properties, key, defaultValue); -} - -void properties_set(properties_pt properties, const char* key, const char* value) { - celix_properties_set(properties, key, value); -} - -void properties_unset(properties_pt properties, const char* key) { - celix_properties_unset(properties, key); -} - -static void updateBuffers(char **key, char ** value, char **output, int outputPos, int *key_len, int *value_len) { - if (*output == *key) { - if (outputPos == (*key_len) - 1) { - (*key_len) += MALLOC_BLOCK_SIZE; - *key = realloc(*key, *key_len); - *output = *key; - } - } - else { - if (outputPos == (*value_len) - 1) { - (*value_len) += MALLOC_BLOCK_SIZE; - *value = realloc(*value, *value_len); - *output = *value; - } - } -} - -static void parseLine(const char* line, celix_properties_t *props) { - int linePos = 0; - bool precedingCharIsBackslash = false; - bool isComment = false; - int outputPos = 0; - char *output = NULL; - int key_len = MALLOC_BLOCK_SIZE; - int value_len = MALLOC_BLOCK_SIZE; - linePos = 0; - precedingCharIsBackslash = false; - isComment = false; - output = NULL; - outputPos = 0; - - //Ignore empty lines - if (line[0] == '\n' && line[1] == '\0') { - return; - } - - char *key = calloc(1, key_len); - char *value = calloc(1, value_len); - key[0] = '\0'; - value[0] = '\0'; - - while (line[linePos] != '\0') { - if (line[linePos] == ' ' || line[linePos] == '\t') { - if (output == NULL) { - //ignore - linePos += 1; - continue; - } - } - else { - if (output == NULL) { - output = key; - } - } - if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') { - if (precedingCharIsBackslash) { - //escaped special character - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - precedingCharIsBackslash = false; - } - else { - if (line[linePos] == '#' || line[linePos] == '!') { - if (outputPos == 0) { - isComment = true; - break; - } - else { - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - } - else { // = or : - if (output == value) { //already have a seperator - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - else { - output[outputPos++] = '\0'; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - output = value; - outputPos = 0; - } - } - } - } - else if (line[linePos] == '\\') { - if (precedingCharIsBackslash) { //double backslash -> backslash - output[outputPos++] = '\\'; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - precedingCharIsBackslash = true; - } - else { //normal character - precedingCharIsBackslash = false; - output[outputPos++] = line[linePos]; - updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - linePos += 1; - } - if (output != NULL) { - output[outputPos] = '\0'; - } - - if (!isComment) { - //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); - celix_properties_set(props, utils_stringTrim(key), utils_stringTrim(value)); - } - if(key) { - free(key); - } - if(value) { - free(value); - } - -} - - - -/********************************************************************************************************************** - ********************************************************************************************************************** - * Updated API - ********************************************************************************************************************** - **********************************************************************************************************************/ - - - -celix_properties_t* celix_properties_create(void) { - return hashMap_create(utils_stringHash, utils_stringHash, utils_stringEquals, utils_stringEquals); -} - -void celix_properties_destroy(celix_properties_t *properties) { - if (properties != NULL) { - hash_map_iterator_pt iter = hashMapIterator_create(properties); - while (hashMapIterator_hasNext(iter)) { - hash_map_entry_pt entry = hashMapIterator_nextEntry(iter); - hashMapEntry_clear(entry, true, true); - } - hashMapIterator_destroy(iter); - hashMap_destroy(properties, false, false); - } -} - -celix_properties_t* celix_properties_load(const char *filename) { - FILE *file = fopen(filename, "r"); - if (file == NULL) { - return NULL; - } - celix_properties_t *props = celix_properties_loadWithStream(file); - fclose(file); - return props; -} - -celix_properties_t* celix_properties_loadWithStream(FILE *file) { - celix_properties_t *props = NULL; - - if (file != NULL ) { - char *saveptr; - char *filebuffer = NULL; - char *line = NULL; - size_t file_size = 0; - - props = celix_properties_create(); - fseek(file, 0, SEEK_END); - file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - if (file_size > 0) { - filebuffer = calloc(file_size + 1, sizeof(char)); - if (filebuffer) { - size_t rs = fread(filebuffer, sizeof(char), file_size, file); - if (rs != file_size) { - fprintf(stderr,"fread read only %lu bytes out of %lu\n", (long unsigned int) rs, (long unsigned int) file_size); - } - filebuffer[file_size]='\0'; - line = strtok_r(filebuffer, "\n", &saveptr); - while (line != NULL) { - parseLine(line, props); - line = strtok_r(NULL, "\n", &saveptr); - } - free(filebuffer); - } - } - } - - return props; -} - -celix_properties_t* celix_properties_loadFromString(const char *input) { - celix_properties_t *props = celix_properties_create(); - - char *in = strdup(input); - char *line = NULL; - char *saveLinePointer = NULL; - - bool firstTime = true; - do { - if (firstTime){ - line = strtok_r(in, "\n", &saveLinePointer); - firstTime = false; - }else { - line = strtok_r(NULL, "\n", &saveLinePointer); - } - - if (line == NULL){ - break; - } - - parseLine(line, props); - } while(line != NULL); - - free(in); - - return props; -} - -void celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { - FILE *file = fopen (filename, "w+" ); - char *str; - - if (file != NULL) { - if (hashMap_size(properties) > 0) { - hash_map_iterator_pt iterator = hashMapIterator_create(properties); - while (hashMapIterator_hasNext(iterator)) { - hash_map_entry_pt entry = hashMapIterator_nextEntry(iterator); - str = hashMapEntry_getKey(entry); - for (int i = 0; i < strlen(str); i += 1) { - if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') { - fputc('\\', file); - } - fputc(str[i], file); - } - - fputc('=', file); - - str = hashMapEntry_getValue(entry); - for (int i = 0; i < strlen(str); i += 1) { - if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') { - fputc('\\', file); - } - fputc(str[i], file); - } - - fputc('\n', file); - - } - hashMapIterator_destroy(iterator); - } - fclose(file); - } else { - perror("File is null"); - } -} - -celix_properties_t* celix_properties_copy(const celix_properties_t *properties) { - celix_properties_t *copy = celix_properties_create(); - if (properties != NULL) { - hash_map_iterator_t iter = hashMapIterator_construct((hash_map_t*)properties); - while (hashMapIterator_hasNext(&iter)) { - hash_map_entry_pt entry = hashMapIterator_nextEntry(&iter); - char *key = hashMapEntry_getKey(entry); - char *value = hashMapEntry_getValue(entry); - celix_properties_set(copy, key, value); - } - } - return copy; -} - -const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { - const char* value = NULL; - if (properties != NULL) { - value = hashMap_get((hash_map_t*)properties, (void*)key); - } - return value == NULL ? defaultValue : value; -} - -void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { - if (properties != NULL) { - hash_map_entry_pt entry = hashMap_getEntry(properties, key); - char *oldVal = NULL; - char *newVal = value == NULL ? NULL : strndup(value, 1024 * 1024); - if (entry != NULL) { - char *oldKey = hashMapEntry_getKey(entry); - oldVal = hashMapEntry_getValue(entry); - hashMap_put(properties, oldKey, newVal); - } else { - hashMap_put(properties, strndup(key, 1024 * 1024), newVal); - } - free(oldVal); - } -} - -void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { - if (properties != NULL) { - hash_map_entry_pt entry = hashMap_getEntry(properties, key); - char *oldVal = NULL; - if (entry != NULL) { - char *oldKey = hashMapEntry_getKey(entry); - oldVal = hashMapEntry_getValue(entry); - hashMap_put(properties, oldKey, value); - } else { - hashMap_put(properties, key, value); - } - free(oldVal); - } -} - -void celix_properties_unset(celix_properties_t *properties, const char *key) { - char* oldValue = hashMap_removeFreeKey(properties, key); - free(oldValue); -} - -long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue) { - long result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { - char *enptr = NULL; - errno = 0; - long r = strtol(val, &enptr, 10); - if (enptr != val && errno == 0) { - result = r; - } - } - return result; -} - -void celix_properties_setLong(celix_properties_t *props, const char *key, long value) { - char buf[32]; //should be enough to store long long int - int writen = snprintf(buf, 32, "%li", value); - if (writen <= 31) { - celix_properties_set(props, key, buf); - } else { - fprintf(stderr,"buf to small for value '%li'\n", value); - } -} - -double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { - double result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { - char *enptr = NULL; - errno = 0; - double r = strtod(val, &enptr); - if (enptr != val && errno == 0) { - result = r; - } - } - return result; -} - -void celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { - char buf[32]; //should be enough to store long long int - int writen = snprintf(buf, 32, "%f", val); - if (writen <= 31) { - celix_properties_set(props, key, buf); - } else { - fprintf(stderr,"buf to small for value '%f'\n", val); - } -} - -bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { - bool result = defaultValue; - const char *val = celix_properties_get(props, key, NULL); - if (val != NULL) { - char buf[32]; - snprintf(buf, 32, "%s", val); - char *trimmed = utils_stringTrim(buf); - if (strncasecmp("true", trimmed, strlen("true")) == 0) { - result = true; - } else if (strncasecmp("false", trimmed, strlen("false")) == 0) { - result = false; - } - } - return result; -} - -void celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { - celix_properties_set(props, key, val ? "true" : "false"); -} - -int celix_properties_size(const celix_properties_t *properties) { - return hashMap_size((hash_map_t*)properties); -} - -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties) { - celix_properties_iterator_t iter; - CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_t) == sizeof(hash_map_iterator_t)); - CELIX_BUILD_ASSERT(__alignof__(celix_properties_iterator_t) == __alignof__(hash_map_iterator_t)); - hash_map_iterator_t mapIter = hashMapIterator_construct((hash_map_t*)properties); - iter._data1 = mapIter.map; - iter._data2 = mapIter.next; - iter._data3 = mapIter.current; - iter._data4 = mapIter.expectedModCount; - iter._data5 = mapIter.index; - return iter; -} - -bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) { - hash_map_iterator_t mapIter; - mapIter.map = iter->_data1; - mapIter.next = iter->_data2; - mapIter.current = iter->_data3; - mapIter.expectedModCount = iter->_data4; - mapIter.index = iter->_data5; - bool hasNext = hashMapIterator_hasNext(&mapIter); - return hasNext; -} -const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) { - hash_map_iterator_t mapIter; - mapIter.map = iter->_data1; - mapIter.next = iter->_data2; - mapIter.current = iter->_data3; - mapIter.expectedModCount = iter->_data4; - mapIter.index = iter->_data5; - const char* result = (const char*)hashMapIterator_nextKey(&mapIter); - iter->_data1 = mapIter.map; - iter->_data2 = mapIter.next; - iter->_data3 = mapIter.current; - iter->_data4 = mapIter.expectedModCount; - iter->_data5 = mapIter.index; - return result; -} - -celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter) { - return (celix_properties_t*)iter->_data1; -} From efb74c19a30c51ad14f4c1a0a50ce5ce31208030 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 16:32:54 +0100 Subject: [PATCH 10/63] Fix Properties implementation for C++11 and C++14. --- libs/utils/gtest/src/VersionTestSuite.cc | 6 -- libs/utils/include/celix/Properties.h | 102 ++++++++++++++++++----- libs/utils/include/celix/Version.h | 67 ++++++++++++--- libs/utils/include/celix_version.h | 11 +++ 4 files changed, 149 insertions(+), 37 deletions(-) diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc index 54539c7b9..5707e4272 100644 --- a/libs/utils/gtest/src/VersionTestSuite.cc +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -150,8 +150,6 @@ TEST_F(VersionTestSuite, CreateEmptyVersionTest) { } TEST_F(VersionTestSuite, CompareTest) { - //TODO update to updated API - celix_version_t* version = nullptr; celix_version_t* compare = nullptr; celix_status_t status = CELIX_SUCCESS; @@ -227,8 +225,6 @@ TEST_F(VersionTestSuite, CompareToMajorMinorTest) { } TEST_F(VersionTestSuite, ToStringTest) { - //TODO update to updated API - celix_version_t* version = nullptr; celix_status_t status = CELIX_SUCCESS; char * str; @@ -262,8 +258,6 @@ TEST_F(VersionTestSuite, ToStringTest) { } TEST_F(VersionTestSuite, SemanticCompatibilityTest) { - //TODO update to updated API - celix_version_t* provider = nullptr; celix_version_t* compatible_user = nullptr; celix_version_t* incompatible_user_by_major = nullptr; diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 5be5a7621..e451ddc1d 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -23,6 +23,12 @@ #include #include #include +#if __cplusplus >= 201703L //C++17 or higher +#include +#else +#include +#endif + #include "celix_properties.h" #include "celix_utils.h" @@ -31,6 +37,48 @@ namespace celix { + +#if __cplusplus < 201703L //lower than C++17 + template + struct IsIntegralDoubleBoolOrVersion : std::false_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template <> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template<> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template<> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template<> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template<> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; + + template<> + struct IsIntegralDoubleBoolOrVersion : std::true_type {}; +#endif + /** * @brief A iterator for celix::Properties. */ @@ -431,7 +479,7 @@ namespace celix { [[nodiscard]] bool getAsBool(const std::string &key, bool defaultValue) const { return celix_properties_getAsBool(cProps.get(), key.c_str(), defaultValue); } - + /** * @brief Get the value of the property with key as a Celix version. * @@ -454,7 +502,7 @@ namespace celix { celix_version_destroy(cVersion); return version; } - return defaultValue + return defaultValue; } /** @@ -478,16 +526,6 @@ namespace celix { celix_properties_set(cProps.get(), key.data(), value.c_str()); } - /** - * @brief Set the value of a property to a boolean. - * - * @param[in] key The key of the property to set. - * @param[in] value The boolean value to set the property to. - */ - void set(const std::string& key, bool value) { - celix_properties_setBool(cProps.get(), key.data(), value); - } - /** * @brief Set the value of a property. * @@ -509,9 +547,21 @@ namespace celix { * @param[in] value The value to set for the property. */ template - void set(const std::string& key, T&& value) { + typename std::enable_if::value, void>::type + set(const std::string& key, T&& value) { + std::cout << "Setting with T&&" << std::endl; using namespace std; - celix_properties_set(cProps.get(), key.data()(), to_string(value).c_str()); + celix_properties_set(cProps.get(), key.data(), to_string(value).c_str()); + } + + /** + * @brief Sets a celix::Version property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, celix::Version&& value) { + celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); } /** @@ -535,23 +585,33 @@ namespace celix { } /** - * @brief Sets a double property value for a given key. + * @brief Sets a long property value for a given key. * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. */ - void set(const std::string& key, double value) { - celix_properties_setDouble(cProps.get(), key.data(), value); + void set(const std::string& key, int value) { + celix_properties_setLong(cProps.get(), key.data(), value); } /** - * @brief Sets a celix::Version property value for a given key. + * @brief Sets a long property value for a given key. * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. */ - void set(const std::string& key, const celix::Version& value) { - celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); + void set(const std::string& key, unsigned int value) { + celix_properties_setLong(cProps.get(), key.data(), value); + } + + /** + * @brief Sets a double property value for a given key. + * + * @param[in] key The key of the property to set. + * @param[in] value The value to set for the property. + */ + void set(const std::string& key, double value) { + celix_properties_setDouble(cProps.get(), key.data(), value); } /** @@ -643,7 +703,7 @@ namespace celix { private: Properties(celix_properties_t* props, bool takeOwnership) : - cProps{props, [ownership = takeOwnership](celix_properties_t* p){ if (ownership) { celix_properties_destroy(p); }}} {} + cProps{props, [takeOwnership](celix_properties_t* p){ if (takeOwnership) { celix_properties_destroy(p); }}} {} static celix::Properties::ValueType getAndConvertType( const std::shared_ptr& cProperties, diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h index c8d2943d4..556f4e92a 100644 --- a/libs/utils/include/celix/Version.h +++ b/libs/utils/include/celix/Version.h @@ -26,14 +26,34 @@ namespace celix { - //TODO doxygen + /** + * @class Version + * @brief Class for storing and manipulating version information. + * + * The Version class represents a version number that follows the Semantic Versioning specification (SemVer). + * It consists of three non-negative integers for the major, minor, and micro version, and an optional string for + * the qualifier. + * The Version class provides comparison operators and functions for getting the individual version components. + * + * @note This class is a thin wrapper around the C API defined in celix_version.h. + */ class Version { public: + + ///@brief Constructs a new empty version with all components set to zero. Version() : cVersion{createVersion(celix_version_createEmptyVersion())}, qualifier{celix_version_getQualifier(cVersion.get())} {} #if __cplusplus >= 201703L //C++17 or higher + + /** + * @brief Constructs a new version with the given components and qualifier. + * @param major The major component of the version. + * @param minor The minor component of the version. + * @param micro The micro component of the version. + * @param qualifier The qualifier string of the version. + */ Version(int major, int minor, int micro, std::string_view qualifier = {}) : cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))}, qualifier{celix_version_getQualifier(cVersion.get())} {} @@ -43,26 +63,31 @@ namespace celix { qualifier{celix_version_getQualifier(cVersion.get())} {} #endif - + ///@brief Move-constructs a new version from an existing one. Version(Version&&) = default; + + ///@brief Copy constructor for a Celix Version object. Version(const Version& rhs) = default; + ///@brief Move assignment operator for the Celix Version class. Version& operator=(Version&&) = default; + + ///@brief Copy assignment operator for the Celix Version class. Version& operator=(const Version& rhs) = default; + ///@brief Test whether two Version objects are equal. bool operator==(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) == 0; } + ///@brief Overload the < operator to compare two Version objects. bool operator<(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) < 0; } - //TODO rest of the operators, is that needed? - /** - * @brief Warps a C Celix Version to a C++ Celix Version, but takes no ownership. - * Dealloction is still the responsibility of the caller. + * @brief Warp a C Celix Version to a C++ Celix Version, but takes no ownership. + * De-allocation is still the responsibility of the caller. */ static Version wrap(celix_version_t* v) { return Version{v}; @@ -78,22 +103,38 @@ namespace celix { return cVersion.get(); } - //TODO doc + /** + * @brief Get the major component of the version. + * The major component designates the primary release. + * @return The major component of the version. + */ [[nodiscard]] int getMajor() const { return celix_version_getMajor(cVersion.get()); } - //TODO doc + /** + * @brief Get the minor component of the version. + * The minor component designates a new or improved feature. + * @return The minor component of the version. + */ [[nodiscard]] int getMinor() const { return celix_version_getMinor(cVersion.get()); } - //TODO doc + /** + * @brief Get the micro component of the version. + * The micro component designates a bug fix. + * @return The micro component of the version. + */ [[nodiscard]] int getMicro() const { return celix_version_getMicro(cVersion.get()); } - //TODO doc + /** + * @brief Get the qualifier component of the version. + * The qualifier component designates additional information about the version. + * @return The qualifier component of the version. + */ [[nodiscard]] const std::string& getQualifier() const { return qualifier; } @@ -117,6 +158,12 @@ namespace celix { } namespace std { + + /** + * @brief The hash celix::Version struct provides a std::hash compatible hashing function for the celix::Version + * class. This allows celix::Version objects to be used as keys in std::unordered_map and std::unordered_set. + * @see std::hash + */ template<> struct hash { size_t operator()(const celix::Version& v) const { diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index f4cea3907..c539dd561 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -26,6 +26,17 @@ extern "C" { #include + +/** + * @file celix_version.h + * @brief Header file for the Celix Version API. + * + * The Celix Version API provides a means for storing and manipulating version information, which consists of + * three non-negative integers for the major, minor, and micro version, and an optional string for the qualifier. + * This implementation is based on the Semantic Versioning specification (SemVer). + * Functions are provided for creating and destroying version objects, comparing versions, and extracting the individual version components. + */ + /** * @brief The definition of the celix_version_t* abstract data type. */ From 4f86a3d3ac585446710fa1662c2b8665381a0063 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 22:12:33 +0100 Subject: [PATCH 11/63] Improve codecov and replace usage of CELIX_PROPERTIES_FOR_EACH with _ITERATE --- .../private/src/pubsub_subscriber.c | 6 +- .../gtest/PubSubInterceptorTestSuite.cc | 5 +- .../src/pubsub_udpmc_admin.c | 5 +- .../src/pubsub_websocket_admin.c | 10 +- .../src/pubsub_discovery_impl.c | 6 +- .../src/pubsub_wire_protocol_common.c | 9 +- .../pubsub/pubsub_spi/src/pubsub_endpoint.c | 5 +- .../src/endpoint_descriptor_writer.c | 11 +- .../src/remote_service_admin_dfi.c | 15 +-- bundles/shell/shell/src/query_command.c | 12 +- .../include/celix/dm/DependencyManager_Impl.h | 7 +- libs/framework/src/celix_launcher.c | 30 ++--- libs/framework/src/service_reference.c | 5 +- .../utils/gtest/src/CxxPropertiesTestSuite.cc | 7 +- libs/utils/gtest/src/CxxVersionTestSuite.cc | 2 + libs/utils/gtest/src/PropertiesTestSuite.cc | 112 +++++++++++++++--- libs/utils/include/celix/Version.h | 3 +- libs/utils/include/celix_properties.h | 10 -- libs/utils/src/properties.c | 36 +++--- 19 files changed, 165 insertions(+), 131 deletions(-) diff --git a/bundles/pubsub/examples/pubsub/subscriber/private/src/pubsub_subscriber.c b/bundles/pubsub/examples/pubsub/subscriber/private/src/pubsub_subscriber.c index 272ff746e..c05df1ee0 100644 --- a/bundles/pubsub/examples/pubsub/subscriber/private/src/pubsub_subscriber.c +++ b/bundles/pubsub/examples/pubsub/subscriber/private/src/pubsub_subscriber.c @@ -62,10 +62,8 @@ int pubsub_subscriber_recv(void* handle, const char* msgType, unsigned int msgTy if (metadata == NULL || celix_properties_size(metadata) == 0) { printf("No metadata\n"); } else { - const char *key; - CELIX_PROPERTIES_FOR_EACH(metadata, key) { - const char *val = celix_properties_get(metadata, key, "!Error!"); - printf("%s=%s\n", key, val); + CELIX_PROPERTIES_ITERATE(metadata, iter) { + printf("%s=%s\n", iter.key, iter.entry.value); } } diff --git a/bundles/pubsub/integration/gtest/PubSubInterceptorTestSuite.cc b/bundles/pubsub/integration/gtest/PubSubInterceptorTestSuite.cc index e8218ccf4..0c4c7c0b3 100644 --- a/bundles/pubsub/integration/gtest/PubSubInterceptorTestSuite.cc +++ b/bundles/pubsub/integration/gtest/PubSubInterceptorTestSuite.cc @@ -110,9 +110,8 @@ class PubSubInterceptorTestSuite : public ::testing::Test { const auto *msg = static_cast(rawMsg); EXPECT_GE(msg->seqNr, 0); EXPECT_STREQ(celix_properties_get(metadata, "test", nullptr), "preSend"); - const char *key; - CELIX_PROPERTIES_FOR_EACH(metadata, key) { - printf("got property %s=%s\n", key, celix_properties_get(metadata, key, nullptr)); + CELIX_PROPERTIES_ITERATE(metadata, iter) { + printf("got property %s=%s\n", iter.key, iter.entry.value); } fprintf(stdout, "Got message in postSend interceptor %s/%s for type %s and ser %s with seq nr %i\n", intProps->scope, intProps->topic, intProps->psaType, intProps->serializationType, msg->seqNr); diff --git a/bundles/pubsub/pubsub_admin_udp_mc/src/pubsub_udpmc_admin.c b/bundles/pubsub/pubsub_admin_udp_mc/src/pubsub_udpmc_admin.c index 9257ac876..91ec6b1bf 100644 --- a/bundles/pubsub/pubsub_admin_udp_mc/src/pubsub_udpmc_admin.c +++ b/bundles/pubsub/pubsub_admin_udp_mc/src/pubsub_udpmc_admin.c @@ -455,9 +455,8 @@ static celix_status_t pubsub_udpmcAdmin_connectEndpointToReceiver(pubsub_udpmc_a if (sockAddress == NULL || sockPort < 0) { L_WARN("[PSA UPDMC] Error got endpoint without udpmc socket address/port or endpoint type. Properties:"); - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(endpoint, key) { - L_WARN("[PSA UPDMC] |- %s=%s\n", key, celix_properties_get(endpoint, key, NULL)); + CELIX_PROPERTIES_ITERATE(endpoint, iter) { + L_WARN("[PSA UPDMC] |- %s=%s\n", iter.key, iter.entry.value); } status = CELIX_BUNDLE_EXCEPTION; } else { diff --git a/bundles/pubsub/pubsub_admin_websocket/src/pubsub_websocket_admin.c b/bundles/pubsub/pubsub_admin_websocket/src/pubsub_websocket_admin.c index 2a103eec2..765e416d9 100644 --- a/bundles/pubsub/pubsub_admin_websocket/src/pubsub_websocket_admin.c +++ b/bundles/pubsub/pubsub_admin_websocket/src/pubsub_websocket_admin.c @@ -379,9 +379,8 @@ static celix_status_t pubsub_websocketAdmin_connectEndpointToReceiver(pubsub_web if (publisher && (sockAddress == NULL || sockPort < 0)) { L_WARN("[PSA WEBSOCKET] Error got endpoint without websocket address/port or endpoint type. Properties:"); - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(endpoint, key) { - L_WARN("[PSA WEBSOCKET] |- %s=%s\n", key, celix_properties_get(endpoint, key, NULL)); + CELIX_PROPERTIES_ITERATE(endpoint, iter) { + L_WARN("[PSA WEBSOCKET] |- %s=%s\n", iter.key, iter.entry.value); } status = CELIX_BUNDLE_EXCEPTION; } else { @@ -436,9 +435,8 @@ static celix_status_t pubsub_websocketAdmin_disconnectEndpointFromReceiver(pubsu if (publisher && (sockAddress == NULL || sockPort < 0)) { L_WARN("[PSA WEBSOCKET] Error got endpoint without websocket address/port or endpoint type. Properties:"); - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(endpoint, key) { - L_WARN("[PSA WEBSOCKET] |- %s=%s\n", key, celix_properties_get(endpoint, key, NULL)); + CELIX_PROPERTIES_ITERATE(endpoint, iter) { + L_WARN("[PSA WEBSOCKET] |- %s=%s\n", iter.key, iter.entry.value); } status = CELIX_BUNDLE_EXCEPTION; } else if (eTopic != NULL && strncmp(eTopic, topic, 1024 * 1024) == 0) { diff --git a/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c b/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c index 4815bd920..d7c2501d5 100644 --- a/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c +++ b/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c @@ -557,10 +557,8 @@ static char* pubsub_discovery_createJsonEndpoint(const celix_properties_t *props //note props is already check for validity (pubsubEndpoint_isValid) json_t *jsEndpoint = json_object(); - const char* propKey = NULL; - CELIX_PROPERTIES_FOR_EACH(props, propKey) { - const char* val = celix_properties_get(props, propKey, NULL); - json_object_set_new(jsEndpoint, propKey, json_string(val)); + CELIX_PROPERTIES_ITERATE(props, iter) { + json_object_set_new(jsEndpoint, iter.key, json_string(iter.entry.value)); } char* str = json_dumps(jsEndpoint, JSON_COMPACT); json_decref(jsEndpoint); diff --git a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c index 1d123d7b7..390b43e41 100644 --- a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c +++ b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c @@ -199,16 +199,13 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t *message, size_t len = 0; if (message->metadata.metadata != NULL && celix_properties_size(message->metadata.metadata) > 0) { - const char *key; char *keyNetString = NULL; int netStringMemoryLength = 0; - CELIX_PROPERTIES_FOR_EACH(message->metadata.metadata, key) { - const char *val = celix_properties_get(message->metadata.metadata, key, "!Error!"); - + CELIX_PROPERTIES_ITERATE(message->metadata.metadata, iter) { //refactoring these two copies to a function leads to a slow down of about 2x int strlenKeyNetString = 0; - status = pubsubProtocol_createNetstring(key, &keyNetString, &strlenKeyNetString, &netStringMemoryLength); + status = pubsubProtocol_createNetstring(iter.key, &keyNetString, &strlenKeyNetString, &netStringMemoryLength); if (status != CELIX_SUCCESS) { break; } @@ -227,7 +224,7 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t *message, memcpy(line + idx, keyNetString, strlenKeyNetString); idx += strlenKeyNetString; - status = pubsubProtocol_createNetstring(val, &keyNetString, &strlenKeyNetString, &netStringMemoryLength); + status = pubsubProtocol_createNetstring(iter.entry.value, &keyNetString, &strlenKeyNetString, &netStringMemoryLength); if (status != CELIX_SUCCESS) { break; } diff --git a/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c b/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c index 9d1ffe9e5..25f133e15 100644 --- a/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c +++ b/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c @@ -46,9 +46,8 @@ static void pubsubEndpoint_setFields(celix_properties_t *ep, const char* fwUUID, //copy topic properties if (topic_props != NULL) { - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH((celix_properties_t *) topic_props, key) { - celix_properties_set(ep, key, celix_properties_get(topic_props, key, NULL)); + CELIX_PROPERTIES_ITERATE((celix_properties_t *) topic_props, iter) { + celix_properties_set(ep, iter.key, iter.entry.value); } } diff --git a/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c b/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c index 66580b5a6..5c4b585cb 100644 --- a/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c +++ b/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c @@ -139,16 +139,15 @@ static celix_status_t endpointDescriptorWriter_writeEndpoint(endpoint_descriptor } else { xmlTextWriterStartElement(writer->writer, ENDPOINT_DESCRIPTION); - const char* propertyName; - CELIX_PROPERTIES_FOR_EACH(endpoint->properties, propertyName) { - const xmlChar* propertyValue = (const xmlChar*) celix_properties_get(endpoint->properties, propertyName, ""); + CELIX_PROPERTIES_ITERATE(endpoint->properties, iter) { + const xmlChar* propertyValue = (const xmlChar*) celix_properties_get(endpoint->properties, iter.key, ""); xmlTextWriterStartElement(writer->writer, PROPERTY); - xmlTextWriterWriteAttribute(writer->writer, NAME, (const xmlChar*)propertyName); + xmlTextWriterWriteAttribute(writer->writer, NAME, (const xmlChar*)iter.key); - if (strcmp(OSGI_FRAMEWORK_OBJECTCLASS, (char*) propertyName) == 0) { + if (strcmp(OSGI_FRAMEWORK_OBJECTCLASS, (char*) iter.key) == 0) { // objectClass *must* be represented as array of string values... endpointDescriptorWriter_writeArrayValue(writer->writer, propertyValue); - } else if (strcmp(OSGI_RSA_ENDPOINT_SERVICE_ID, (char*) propertyName) == 0) { + } else if (strcmp(OSGI_RSA_ENDPOINT_SERVICE_ID, (char*) iter.key) == 0) { // endpoint.service.id *must* be represented as long value... endpointDescriptorWriter_writeTypedValue(writer->writer, VALUE_TYPE_LONG, propertyValue); } else { diff --git a/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c b/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c index 656b37734..2b5fc3356 100644 --- a/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c +++ b/bundles/remote_services/remote_service_admin_dfi/src/remote_service_admin_dfi.c @@ -747,9 +747,8 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic celix_properties_set(endpointProperties, RSA_DFI_ENDPOINT_URL, url); if (props != NULL) { - const char* key; - CELIX_PROPERTIES_FOR_EACH(props, key) { - celix_properties_set(endpointProperties, key, celix_properties_get(props, key, "")); + CELIX_PROPERTIES_ITERATE(props, iter) { + celix_properties_set(endpointProperties, iter.key, iter.entry.value); } } @@ -947,14 +946,10 @@ static celix_status_t remoteServiceAdmin_send(void *handle, endpoint_description } else { struct curl_slist *metadataHeader = NULL; if (metadata != NULL && celix_properties_size(metadata) > 0) { - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(metadata, key) { - const char *val = celix_properties_get(metadata, key, ""); - size_t length = strlen(key) + strlen(val) + 18; // "X-RSA-Metadata-key: val\0" - + CELIX_PROPERTIES_ITERATE(metadata, iter) { + size_t length = strlen(iter.key) + strlen(iter.entry.value) + 18; // "X-RSA-Metadata-key: val\0" char header[length]; - - snprintf(header, length, "X-RSA-Metadata-%s: %s", key, val); + snprintf(header, length, "X-RSA-Metadata-%s: %s", iter.key, iter.entry.value); metadataHeader = curl_slist_append(metadataHeader, header); } diff --git a/bundles/shell/shell/src/query_command.c b/bundles/shell/shell/src/query_command.c index 739d34500..93cf69cb8 100644 --- a/bundles/shell/shell/src/query_command.c +++ b/bundles/shell/shell/src/query_command.c @@ -95,18 +95,10 @@ static void queryCommand_callback(void *handle, const celix_bundle_t *bnd) { queryCommand_printBundleHeader(data->sout, bnd, &printBundleCalled); fprintf(data->sout, "|- Provided service '%s' [id = %li]\n", entry->serviceName, entry->serviceId); if (data->opts->verbose) { - const char *cmpUUID = celix_properties_get(entry->serviceProperties, "component.uuid", NULL); - if (cmpUUID != NULL) { - //TODO add context to opts - //TODO add celix_dependencyManager_createInfoForUUID() } - //TODO print component name - } fprintf(data->sout, " |- Is factory: %s\n", entry->factory ? "true" : "false"); fprintf(data->sout, " |- Properties:\n"); - const char *key; - CELIX_PROPERTIES_FOR_EACH(entry->serviceProperties, key) { - const char *val = celix_properties_get(entry->serviceProperties, key, "!ERROR!"); - fprintf(data->sout, " |- %20s = %s\n", key, val); + CELIX_PROPERTIES_ITERATE(entry->serviceProperties, iter) { + fprintf(data->sout, " |- %20s = %s\n", iter.key, iter.entry.value); } } } diff --git a/libs/framework/include/celix/dm/DependencyManager_Impl.h b/libs/framework/include/celix/dm/DependencyManager_Impl.h index f3e9f075d..05e262d96 100644 --- a/libs/framework/include/celix/dm/DependencyManager_Impl.h +++ b/libs/framework/include/celix/dm/DependencyManager_Impl.h @@ -174,10 +174,8 @@ static celix::dm::DependencyManagerInfo createDepManInfoFromC(celix_dependency_m auto* cIntInfo = static_cast(celix_arrayList_get(cCmpInfo->interfaces, k)); celix::dm::InterfaceInfo intInfo{}; intInfo.serviceName = std::string{cIntInfo->name}; - const char* key; - CELIX_PROPERTIES_FOR_EACH(cIntInfo->properties, key) { - const char* val =celix_properties_get(cIntInfo->properties, key, nullptr); - intInfo.properties[std::string{key}] = std::string{val}; + CELIX_PROPERTIES_ITERATE(cIntInfo->properties, iter) { + intInfo.properties[std::string{iter.key}] = std::string{iter.entry.value}; } cmpInfo.interfacesInfo.emplace_back(std::move(intInfo)); } @@ -206,7 +204,6 @@ inline celix::dm::DependencyManagerInfo DependencyManager::getInfo() const { return result; } - inline std::vector DependencyManager::getInfos() const { std::vector result{}; auto* cInfos = celix_dependencyManager_createInfos(cDependencyManager()); diff --git a/libs/framework/src/celix_launcher.c b/libs/framework/src/celix_launcher.c index 60b0c8ec1..394e0a15d 100644 --- a/libs/framework/src/celix_launcher.c +++ b/libs/framework/src/celix_launcher.c @@ -188,17 +188,15 @@ void celixLauncher_stop(celix_framework_t* framework) { } static void show_properties(celix_properties_t *embeddedProps, const char *configFile) { - const char *key = NULL; celix_properties_t *keys = celix_properties_create(); //only to store the keys printf("Embedded properties:\n"); if (embeddedProps == NULL || celix_properties_size(embeddedProps) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(embeddedProps, key) { - const char *val = celix_properties_get(embeddedProps, key, "!Error!"); - printf("|- %s=%s\n", key, val); - celix_properties_set(keys, key, NULL); + CELIX_PROPERTIES_ITERATE(embeddedProps, iter) { + printf("|- %s=%s\n", iter.key, iter.entry.value); + celix_properties_set(keys, iter.key, NULL); } } printf("\n"); @@ -211,10 +209,9 @@ static void show_properties(celix_properties_t *embeddedProps, const char *confi if (runtimeProps == NULL || celix_properties_size(runtimeProps) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(runtimeProps, key) { - const char *val = celix_properties_get(runtimeProps, key, "!Error!"); - printf("|- %s=%s\n", key, val); - celix_properties_set(keys, key, NULL); + CELIX_PROPERTIES_ITERATE(runtimeProps, iter) { + printf("|- %s=%s\n", iter.key, iter.entry.value); + celix_properties_set(keys, iter.key, NULL); } } printf("\n"); @@ -225,13 +222,13 @@ static void show_properties(celix_properties_t *embeddedProps, const char *confi if (celix_properties_size(keys) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(keys, key) { - const char *valEm = celix_properties_get(embeddedProps, key, NULL); - const char *valRt = celix_properties_get(runtimeProps, key, NULL); - const char *envVal = getenv(key); + CELIX_PROPERTIES_ITERATE(keys, iter) { + const char *valEm = celix_properties_get(embeddedProps, iter.key, NULL); + const char *valRt = celix_properties_get(runtimeProps, iter.key, NULL); + const char *envVal = getenv(iter.key); const char *val = envVal != NULL ? envVal : valRt != NULL ? valRt : valEm; const char *source = envVal != NULL ? "environment" : valRt != NULL ? "runtime" : "embedded"; - printf("|- %s=%s (source %s)\n", key, val, source); + printf("|- %s=%s (source %s)\n", iter.key, val, source); } } printf("\n"); @@ -258,9 +255,8 @@ static void printEmbeddedBundles() { static void combine_properties(celix_properties_t *original, const celix_properties_t *append) { if (original != NULL && append != NULL) { - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(append, key) { - celix_properties_set(original, key, celix_properties_get(append, key, NULL)); + CELIX_PROPERTIES_ITERATE(append, iter) { + celix_properties_set(original, iter.key, iter.entry.value); } } } diff --git a/libs/framework/src/service_reference.c b/libs/framework/src/service_reference.c index a7faebd77..0160ae8ef 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -204,9 +204,8 @@ FRAMEWORK_EXPORT celix_status_t serviceReference_getPropertyKeys(service_referen int vsize = celix_properties_size(props); *size = (unsigned int)vsize; *keys = malloc(vsize * sizeof(**keys)); - const char* key; - CELIX_PROPERTIES_FOR_EACH(props, key) { - (*keys)[i++] = (char*)key; + CELIX_PROPERTIES_ITERATE(props, iter) { + (*keys)[i++] = (char*)iter.key; } return status; } diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index b2f39192a..7d4f35323 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -176,7 +176,12 @@ TEST_F(CxxPropertiesTestSuite, StoreAndLoadTest) { EXPECT_NO_THROW(loadedProps = celix::Properties::load(path)); EXPECT_EQ(props.size(), loadedProps.size()); - EXPECT_THROW(loadedProps = celix::Properties::load("non-existence"), celix::IOException); + try { + loadedProps = celix::Properties::load("non-existence"); + FAIL() << "Expected exception not thrown"; + } catch (const celix::IOException& e) { + EXPECT_TRUE(strstr(e.what(), "Cannot load celix::Properties")); + } } #if __cplusplus >= 201703L //C++17 or higher diff --git a/libs/utils/gtest/src/CxxVersionTestSuite.cc b/libs/utils/gtest/src/CxxVersionTestSuite.cc index 0055d5c4a..ac7266c3f 100644 --- a/libs/utils/gtest/src/CxxVersionTestSuite.cc +++ b/libs/utils/gtest/src/CxxVersionTestSuite.cc @@ -18,6 +18,8 @@ */ #include +#include +#include #include "celix/Version.h" diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index f21b4049c..6904652e0 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -21,6 +21,7 @@ #include "celix_properties.h" #include "celix_utils.h" +#include "properties.h" using ::testing::MatchesRegex; @@ -56,6 +57,16 @@ TEST_F(PropertiesTestSuite, LoadTest) { celix_properties_destroy(properties); } +TEST_F(PropertiesTestSuite, LoadFromStringTest) { + const char* string = "key1=value1\nkey2=value2"; + auto* props = celix_properties_loadFromString(string); + EXPECT_EQ(2, celix_properties_size(props)); + EXPECT_STREQ("value1", celix_properties_get(props, "key1", "")); + EXPECT_STREQ("value2", celix_properties_get(props, "key2", "")); + celix_properties_destroy(props); +} + + TEST_F(PropertiesTestSuite, StoreTest) { const char* propertiesFile = "resources-test/properties_out.txt"; auto* properties = celix_properties_create(); @@ -99,23 +110,6 @@ TEST_F(PropertiesTestSuite, GetAsLongTest) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, CopyTest) { - char propertiesFile[] = "resources-test/properties.txt"; - auto* properties = celix_properties_load(propertiesFile); - EXPECT_EQ(4, celix_properties_size(properties)); - - celix_properties_t *copy = celix_properties_copy(properties); - - char keyA[] = "a"; - const char *valueA = celix_properties_get(copy, keyA, nullptr); - EXPECT_STREQ("b", valueA); - const char keyB[] = "b"; - EXPECT_STREQ("c \t d", celix_properties_get(copy, keyB, nullptr)); - - celix_properties_destroy(properties); - celix_properties_destroy(copy); -} - TEST_F(PropertiesTestSuite, GetSetTest) { auto* properties = celix_properties_create(); char keyA[] = "x"; @@ -169,6 +163,13 @@ TEST_F(PropertiesTestSuite, SetUnsetTest) { celix_properties_unset(properties, keyD); EXPECT_EQ(nullptr, celix_properties_get(properties, keyA, nullptr)); EXPECT_EQ(nullptr, celix_properties_get(properties, "a", nullptr)); + EXPECT_EQ(0, celix_properties_size(properties)); + + celix_properties_set(properties, keyA, nullptr); + EXPECT_EQ(1, celix_properties_size(properties)); + celix_properties_unset(properties, keyA); + EXPECT_EQ(0, celix_properties_size(properties)); + celix_properties_destroy(properties); } @@ -202,6 +203,35 @@ TEST_F(PropertiesTestSuite, GetLongTest) { celix_properties_destroy(properties); } +TEST_F(PropertiesTestSuite, GetAsDoubleTest) { + auto* properties = celix_properties_create(); + + celix_properties_set(properties, "a", "2"); + celix_properties_set(properties, "b", "-10032L"); + celix_properties_set(properties, "c", "1.2"); + celix_properties_setDouble(properties, "d", 1.4); + celix_properties_set(properties, "e", ""); + celix_properties_set(properties, "f", "garbage"); + + double a = celix_properties_getAsDouble(properties, "a", -1); + double b = celix_properties_getAsDouble(properties, "b", -1); + double c = celix_properties_getAsDouble(properties, "c", -1); + double d = celix_properties_getAsDouble(properties, "d", -1); + double e = celix_properties_getAsDouble(properties, "e", -1); + double f = celix_properties_getAsDouble(properties, "f", -1); + double g = celix_properties_getAsDouble(properties, "g", -1); //does not exist + + EXPECT_EQ(2, a); + EXPECT_EQ(-10032L, b); + EXPECT_EQ(1.2, c); + EXPECT_EQ(1.4, d); + EXPECT_EQ(-1L, e); + EXPECT_EQ(-1L, f); + EXPECT_EQ(-1L, g); + + celix_properties_destroy(properties); +} + TEST_F(PropertiesTestSuite, GetBoolTest) { auto* properties = celix_properties_create(); @@ -295,7 +325,7 @@ TEST_F(PropertiesTestSuite, SizeAndIteratorTest) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, GetTypeTest) { +TEST_F(PropertiesTestSuite, GetTypeAndCopyTest) { auto* props = celix_properties_create(); celix_properties_set(props, "string", "value"); celix_properties_setLong(props, "long", 123); @@ -304,6 +334,7 @@ TEST_F(PropertiesTestSuite, GetTypeTest) { auto* version = celix_version_createVersion(1, 2, 3, nullptr); celix_properties_setVersion(props, "version", version); + EXPECT_EQ(5, celix_properties_size(props)); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, celix_properties_getType(props, "string")); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, celix_properties_getType(props, "long")); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, celix_properties_getType(props, "double")); @@ -311,8 +342,17 @@ TEST_F(PropertiesTestSuite, GetTypeTest) { EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props, "version")); EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_UNSET, celix_properties_getType(props, "missing")); + auto* copy = celix_properties_copy(props); + EXPECT_EQ(5, celix_properties_size(copy)); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_STRING, celix_properties_getType(copy, "string")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_LONG, celix_properties_getType(copy, "long")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_DOUBLE, celix_properties_getType(copy, "double")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_BOOL, celix_properties_getType(copy, "bool")); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(copy, "version")); + celix_version_destroy(version); celix_properties_destroy(props); + celix_properties_destroy(copy); } TEST_F(PropertiesTestSuite, GetEntryTest) { @@ -509,4 +549,38 @@ TEST_F(PropertiesTestSuite, EndOfEmptyPropertiesTest) { celix_properties_destroy(props); } -//TODO test replace and replace WithCopy \ No newline at end of file +TEST_F(PropertiesTestSuite, SetWithCopyTest) { + auto* props = celix_properties_create(); + celix_properties_setWithoutCopy(props, celix_utils_strdup("key"), celix_utils_strdup("value2")); + //replace, should free old value and provided key + celix_properties_setWithoutCopy(props, celix_utils_strdup("key"), celix_utils_strdup("value2")); + EXPECT_EQ(1, celix_properties_size(props)); + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, DeprecatedApiTest) { + //Check if the deprecated api can still be used + auto* props = properties_create(); + properties_set(props, "key", "value"); + EXPECT_EQ(1, celix_properties_size(props)); + EXPECT_STREQ("value", properties_get(props, "key")); + EXPECT_STREQ("notfound", properties_getWithDefault(props, "non-existing", "notfound")); + properties_unset(props, "key"); + EXPECT_EQ(0, celix_properties_size(props)); + celix_properties_t * copy = nullptr; + EXPECT_EQ(CELIX_SUCCESS, properties_copy(props, ©)); + properties_destroy(copy); + + + properties_store(props, "deprecated-api-stored.properties", ""); + auto* loaded = properties_load("deprecated-api-stored.properties"); + EXPECT_NE(nullptr, loaded); + properties_destroy(loaded); + + loaded = properties_loadFromString("key=value"); + EXPECT_EQ(1, celix_properties_size(loaded)); + properties_destroy(loaded); + + + properties_destroy(props); +} diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h index 556f4e92a..c019d14a9 100644 --- a/libs/utils/include/celix/Version.h +++ b/libs/utils/include/celix/Version.h @@ -45,8 +45,6 @@ namespace celix { cVersion{createVersion(celix_version_createEmptyVersion())}, qualifier{celix_version_getQualifier(cVersion.get())} {} -#if __cplusplus >= 201703L //C++17 or higher - /** * @brief Constructs a new version with the given components and qualifier. * @param major The major component of the version. @@ -54,6 +52,7 @@ namespace celix { * @param micro The micro component of the version. * @param qualifier The qualifier string of the version. */ +#if __cplusplus >= 201703L //C++17 or higher Version(int major, int minor, int micro, std::string_view qualifier = {}) : cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))}, qualifier{celix_version_getQualifier(cVersion.get())} {} diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 71bc0f536..45bbef172 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -405,14 +405,6 @@ void celix_propertiesIterator_next(celix_properties_iterator_t* iter); */ bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); -/** - * @brief Get the property set being iterated over. - * - * @param[in] iter The iterator to get the property set from. - * @return The property set being iterated over. - */ -celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter); - /** * @brief Determine whether two iterators are equal. * @@ -517,8 +509,6 @@ const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter) for(celix_properties_iterator_t iter_##key = celix_propertiesIterator_construct(properties); \ celix_propertiesIterator_hasNext(&iter_##key), (key) = celix_propertiesIterator_nextKey(&iter_##key);) - - #ifdef __cplusplus } #endif diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index dbbc20481..cf9df2644 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -141,7 +141,10 @@ static void updateBuffers(char **key, char ** value, char **output, int outputPo * optimization string buffer. */ static char* celix_properties_createString(celix_properties_t* properties, const char* str) { - size_t len = str == NULL ? 0 : strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; + if (str == NULL) { + return NULL; + } + size_t len = strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; size_t left = CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - properties->currentStringBufferIndex; char* result; if (len < left) { @@ -160,7 +163,7 @@ static char* celix_properties_createString(celix_properties_t* properties, const static celix_status_t celix_properties_fillEntry( celix_properties_t *properties, celix_properties_entry_t* entry, - const char *strValue, + const char** strValue, const long* longValue, const double* doubleValue, const bool* boolValue, @@ -168,7 +171,7 @@ static celix_status_t celix_properties_fillEntry( char convertedValueBuffer[32]; if (strValue != NULL) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; - entry->value = celix_properties_createString(properties, strValue); + entry->value = celix_properties_createString(properties, *strValue); entry->typed.strValue = entry->value; } else if (longValue != NULL) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; @@ -250,7 +253,7 @@ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_pr static celix_properties_entry_t* celix_properties_createEntry( celix_properties_t *properties, const char *key, - const char *strValue, + const char** strValue, const long* longValue, const double* doubleValue, const bool* boolValue, @@ -276,7 +279,7 @@ static celix_properties_entry_t* celix_properties_createEntry( static void celix_properties_createAndSetEntry( celix_properties_t *properties, const char *key, - const char *strValue, + const char** strValue, const long* longValue, const double* doubleValue, const bool* boolValue, @@ -348,13 +351,13 @@ celix_properties_t* celix_properties_create(void) { void celix_properties_destroy(celix_properties_t *props) { if (props != NULL) { //TODO measure print nr of entries and total size of the string keys and values -// fprintf(stdout, "Properties size; %d", celix_properties_size(props)); -// size_t size = 0; -// CELIX_PROPERTIES_ITERATE(props, iter) { -// size += strlen(iter.entry.key) + 1; -// size += strlen(iter.entry.value) + 1; -// } -// fprintf(stdout, "Properties string size: %zu", size); + fprintf(stdout, "Properties entries size: %d\n", celix_properties_size(props)); + size_t size = 0; + CELIX_PROPERTIES_ITERATE(props, iter) { + size += strlen(iter.key) + 1; + size += strlen(iter.entry.value) + 1; + } + fprintf(stdout, "Properties total string size: %zu\n\n", size); celix_stringHashMap_destroy(props->map); free(props); @@ -628,8 +631,8 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr } void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { - if (properties != NULL && key != NULL && value != NULL) { - celix_properties_createAndSetEntry(properties, key, value, NULL, NULL, NULL, NULL); + if (properties != NULL && key != NULL) { + celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL); } } @@ -863,8 +866,3 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); } -celix_properties_t* celix_propertiesIterator_properties(const celix_properties_iterator_t *iter) { - celix_properties_iterator_internal_t internalIter; - memcpy(&internalIter, iter, sizeof(internalIter)); - return (celix_properties_t*)internalIter.props; -} From c15bf710dc0f20ea14a6469318f035f72e535511 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 22:28:48 +0100 Subject: [PATCH 12/63] Adjust short properties optimization values based on some basic testing --- libs/utils/src/properties.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index cf9df2644..778e00da3 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -33,7 +33,7 @@ #include "celix_string_hash_map.h" -#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 1024 +#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 512 #define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16 static const char* const CELIX_PROPERTIES_BOOL_TRUE_STRVAL = "true"; @@ -43,8 +43,11 @@ struct celix_properties { celix_string_hash_map_t* map; /** - * String buffer used to store the first key/value entries, so that in many cases additional memory allocation - * can be prevented. + * String buffer used to store the first key/value entries, + * so that in many cases - for usage in service properties - additional memory allocations are not needed. + * + * @note based on some small testing most services properties seem to max out at 11 entries. + * So 16 (next factor 2 based value) seems like a good fit. */ char stringBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE]; @@ -56,6 +59,9 @@ struct celix_properties { /** * Entries buffer used to store the first entries, so that in many cases additional memory allocation * can be prevented. + * + * @note based on some small testing most services properties seem to max around 300 bytes. + * So 512 (next factor 2 based value) seems like a good fit. */ celix_properties_entry_t entriesBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE]; @@ -350,15 +356,6 @@ celix_properties_t* celix_properties_create(void) { void celix_properties_destroy(celix_properties_t *props) { if (props != NULL) { - //TODO measure print nr of entries and total size of the string keys and values - fprintf(stdout, "Properties entries size: %d\n", celix_properties_size(props)); - size_t size = 0; - CELIX_PROPERTIES_ITERATE(props, iter) { - size += strlen(iter.key) + 1; - size += strlen(iter.entry.value) + 1; - } - fprintf(stdout, "Properties total string size: %zu\n\n", size); - celix_stringHashMap_destroy(props->map); free(props); } @@ -476,7 +473,6 @@ celix_properties_t* celix_properties_loadWithStream(FILE *file) { return NULL; } - //TODO create properties with no internal short properties buffer, so celix_properties_createWithOptions() celix_properties_t *props = celix_properties_create(); if (props == NULL) { return NULL; From fe434a03fe99bab2228df8dd2292aa3ddf0d13e1 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 8 Jan 2023 22:48:12 +0100 Subject: [PATCH 13/63] Fix issue with setting properties with NULL values --- libs/utils/gtest/src/PropertiesTestSuite.cc | 5 +-- libs/utils/src/properties.c | 40 ++++++++++++--------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 6904652e0..bfc3efbfc 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -141,8 +141,9 @@ TEST_F(PropertiesTestSuite, GetSetWithNullTest) { celix_properties_set(properties, nullptr, nullptr); EXPECT_EQ(celix_properties_size(properties), 0); //NULL key will be ignored - celix_properties_set(properties, "key", nullptr); - EXPECT_EQ(celix_properties_size(properties), 0); //NULL value will be ignored + celix_properties_set(properties, "key", nullptr); //NULL value will result in empty string value + EXPECT_STREQ("", celix_properties_get(properties, "key", "not found")); + EXPECT_EQ(celix_properties_size(properties), 1); celix_properties_destroy(properties); } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 778e00da3..0ef19cc20 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -38,6 +38,7 @@ static const char* const CELIX_PROPERTIES_BOOL_TRUE_STRVAL = "true"; static const char* const CELIX_PROPERTIES_BOOL_FALSE_STRVAL = "false"; +static const char* const CELIX_PROPERTIES_EMPTY_STRVAL = ""; struct celix_properties { celix_string_hash_map_t* map; @@ -148,7 +149,7 @@ static void updateBuffers(char **key, char ** value, char **output, int outputPo */ static char* celix_properties_createString(celix_properties_t* properties, const char* str) { if (str == NULL) { - return NULL; + return (char*)CELIX_PROPERTIES_EMPTY_STRVAL; } size_t len = strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; size_t left = CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - properties->currentStringBufferIndex; @@ -162,6 +163,22 @@ static char* celix_properties_createString(celix_properties_t* properties, const } return result; } +/** + * Free string, but first check if it a static const char* const string or part of the short properties + * optimization. + */ +static void celix_properties_freeString(celix_properties_t* properties, char* str) { + if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || + str == CELIX_PROPERTIES_BOOL_FALSE_STRVAL || + str == CELIX_PROPERTIES_EMPTY_STRVAL) { + //str is static const char* const -> nop + } else if (str >= properties->stringBuffer && + str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { + //str is part of the properties string buffer -> nop + } else { + free(str); + } +} /** * Fill entry and optional use the short properties optimization string buffer. @@ -272,7 +289,12 @@ static celix_properties_entry_t* celix_properties_createEntry( celix_status_t status = celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); if (status != CELIX_SUCCESS) { - free(entry); + if (entry >= properties->entriesBuffer && + entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { + //entry is part of the properties entries buffer -> nop. + } else { + free(entry); + } entry = NULL; } return entry; @@ -305,19 +327,6 @@ static void celix_properties_createAndSetEntry( } } - - -static void celix_properties_freeString(celix_properties_t* properties, char* str) { - if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == CELIX_PROPERTIES_BOOL_FALSE_STRVAL) { - //str is static const char* const -> nop - } else if (str >= properties->stringBuffer && - str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { - //str is part of the properties string buffer -> nop - } else { - free(str); - } -} - static void celix_properties_removeKeyCallback(void* handle, char* key) { celix_properties_t* properties = handle; celix_properties_freeString(properties, key); @@ -861,4 +870,3 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const memcpy(&internalIterB, b->_data, sizeof(internalIterB)); return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); } - From 0c0bf1485337ccab24c41927329a26ce78baa6a5 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 7 May 2023 15:50:56 +0200 Subject: [PATCH 14/63] Refactor properties and version after merge with master --- libs/framework/src/service_reference.c | 32 +++++---- libs/utils/CMakeLists.txt | 13 ---- libs/utils/gtest/CMakeLists.txt | 6 ++ .../ConvertUtilsErrorInjectionTestSuite.cc | 2 +- libs/utils/gtest/src/ConvertUtilsTestSuite.cc | 2 +- libs/utils/gtest/src/PropertiesTestSuite.cc | 8 +-- libs/utils/gtest/src/VersionRangeTestSuite.cc | 4 +- libs/utils/gtest/src/VersionTestSuite.cc | 4 +- libs/utils/include/celix_properties.h | 68 +++++++++---------- libs/utils/include/celix_version.h | 16 +++-- libs/utils/private/test/version_ei_test.cc | 6 +- libs/utils/src/properties.c | 4 +- libs/utils/src/version.c | 10 +-- 13 files changed, 89 insertions(+), 86 deletions(-) diff --git a/libs/framework/src/service_reference.c b/libs/framework/src/service_reference.c index 6c5d1d36c..c207b0486 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -196,23 +196,27 @@ celix_status_t serviceReference_getProperty(service_reference_pt ref, const char } celix_status_t serviceReference_getPropertyKeys(service_reference_pt ref, char **keys[], unsigned int *size) { - celix_status_t status = CELIX_SUCCESS; - properties_pt props = NULL; + if (!keys || !size) { + return CELIX_ILLEGAL_ARGUMENT; + } + + celix_properties_t* props = NULL; + celix_status_t status = serviceRegistration_getProperties(ref->registration, &props); + if (status != CELIX_SUCCESS) { + return status; + } + + *keys = malloc(celix_properties_size(props) * sizeof(**keys)); + if (!*keys) { + return ENOMEM; + } - status = serviceRegistration_getProperties(ref->registration, &props); - assert(status == CELIX_SUCCESS); - hash_map_iterator_pt it; int i = 0; - int vsize = hashMap_size(props); - *size = (unsigned int)vsize; - *keys = malloc(vsize * sizeof(**keys)); - it = hashMapIterator_create(props); - while (hashMapIterator_hasNext(it)) { - (*keys)[i] = hashMapIterator_nextKey(it); - i++; + CELIX_PROPERTIES_ITERATE(props, entry) { + (*keys)[i++] = (char*)entry.key; } - hashMapIterator_destroy(it); - return status; + + return CELIX_SUCCESS; } celix_status_t serviceReference_invalidateCache(service_reference_pt reference) { diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index eca648115..df8909a08 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -143,19 +143,10 @@ if (ENABLE_TESTING) target_include_directories(linked_list_test PRIVATE include_deprecated) target_link_libraries(linked_list_test utils_cut CppUTest::CppUTest pthread) - add_executable(properties_test private/test/properties_test.cpp) - target_include_directories(properties_test PRIVATE include_deprecated) - target_link_libraries(properties_test CppUTest::CppUTest CppUTest::CppUTestExt utils_cut pthread) - add_executable(ip_utils_test private/test/ip_utils_test.cpp) target_include_directories(ip_utils_test PRIVATE include_deprecated) target_link_libraries(ip_utils_test CppUTest::CppUTest utils_cut pthread) - add_executable(version_test private/test/version_test.cpp) - target_include_directories(version_test PRIVATE include_deprecated) - target_link_libraries(version_test CppUTest::CppUTest utils_cut pthread) - - if (LINKER_WRAP_SUPPORTED) add_executable(version_ei_test private/test/version_ei_test.cc) target_include_directories(version_ei_test PRIVATE include_deprecated) @@ -163,14 +154,10 @@ if (ENABLE_TESTING) add_test(NAME version_ei_test COMMAND version_ei_test) endif () - configure_file(private/resources-test/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY) - add_test(NAME run_array_list_test COMMAND array_list_test) add_test(NAME run_hash_map_test COMMAND hash_map_test) add_test(NAME run_linked_list_test COMMAND linked_list_test) - add_test(NAME run_properties_test COMMAND properties_test) add_test(NAME run_ip_utils_test COMMAND ip_utils_test) - add_test(NAME version_test COMMAND version_test) setup_target_for_coverage(array_list_test) setup_target_for_coverage(hash_map_test) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 06af219dc..8fc7264b5 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -21,6 +21,7 @@ set(CELIX_UTIL_TEST_SOURCES_FOR_CXX_HEADERS src/CxxUtilsTestSuite.cc src/CxxPropertiesTestSuite.cc src/CxxFilterTestSuite.cc + src/CxxVersionTestSuite.cc ) add_executable(test_utils @@ -33,6 +34,8 @@ add_executable(test_utils src/FilterTestSuite.cc src/CelixUtilsTestSuite.cc src/ConvertUtilsTestSuite.cc + src/PropertiesTestSuite.cc + src/VersionTestSuite.cc src/ErrTestSuite.cc src/ThreadsTestSuite.cc src/CelixErrnoTestSuite.cc @@ -43,6 +46,9 @@ target_link_libraries(test_utils PRIVATE utils_cut GTest::gtest GTest::gtest_mai target_include_directories(test_utils PRIVATE ../src) #for version_private (needs refactoring of test) celix_deprecated_utils_headers(test_utils) +configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY) + + ####### generating zip file used for testing ########################################################################## file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/top.properties" CONTENT "level=1\n") file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/zip_content/subdir/sub.properties" CONTENT "level=2\n") diff --git a/libs/utils/gtest/src/ConvertUtilsErrorInjectionTestSuite.cc b/libs/utils/gtest/src/ConvertUtilsErrorInjectionTestSuite.cc index 37d3c8a05..730f9dcec 100644 --- a/libs/utils/gtest/src/ConvertUtilsErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/ConvertUtilsErrorInjectionTestSuite.cc @@ -37,7 +37,7 @@ TEST_F(ConvertUtilsWithErrorInjectionTestSuite, CovertToBoolTest) { } TEST_F(ConvertUtilsWithErrorInjectionTestSuite, ConvertToVersionTest) { - celix_version_t* defaultVersion = celix_version_createVersion(1, 2, 3, "B"); + celix_version_t* defaultVersion = celix_version_create(1, 2, 3, "B"); celix_ei_expect_celix_utils_writeOrCreateString(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); celix_version_t* result = celix_utils_convertStringToVersion("1.2.3", nullptr, nullptr); EXPECT_EQ(nullptr, result); diff --git a/libs/utils/gtest/src/ConvertUtilsTestSuite.cc b/libs/utils/gtest/src/ConvertUtilsTestSuite.cc index 91ed43d72..bbe24ada8 100644 --- a/libs/utils/gtest/src/ConvertUtilsTestSuite.cc +++ b/libs/utils/gtest/src/ConvertUtilsTestSuite.cc @@ -204,7 +204,7 @@ TEST_F(ConvertUtilsTestSuite, ConvertToBoolTest) { } TEST_F(ConvertUtilsTestSuite, ConvertToVersionTest) { - celix_version_t* defaultVersion = celix_version_createVersion(1, 2, 3, "B"); + celix_version_t* defaultVersion = celix_version_create(1, 2, 3, "B"); //test for a valid string celix_version_t* result = celix_utils_convertStringToVersion("1.2.3", nullptr, nullptr); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index bfc3efbfc..5317af76a 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -332,7 +332,7 @@ TEST_F(PropertiesTestSuite, GetTypeAndCopyTest) { celix_properties_setLong(props, "long", 123); celix_properties_setDouble(props, "double", 3.14); celix_properties_setBool(props, "bool", true); - auto* version = celix_version_createVersion(1, 2, 3, nullptr); + auto* version = celix_version_create(1, 2, 3, nullptr); celix_properties_setVersion(props, "version", version); EXPECT_EQ(5, celix_properties_size(props)); @@ -362,7 +362,7 @@ TEST_F(PropertiesTestSuite, GetEntryTest) { celix_properties_setLong(props, "key2", 123); celix_properties_setDouble(props, "key3", 123.456); celix_properties_setBool(props, "key4", true); - auto* version = celix_version_createVersion(1, 2, 3, nullptr); + auto* version = celix_version_create(1, 2, 3, nullptr); celix_properties_setVersion(props, "key5", version); auto* entry = celix_properties_getEntry(props, "key1"); @@ -478,7 +478,7 @@ TEST_F(PropertiesTestSuite, GetVersionTest) { auto* emptyVersion = celix_version_createEmptyVersion(); // Test getting a version property - auto* expected = celix_version_createVersion(1, 2, 3, "test"); + auto* expected = celix_version_create(1, 2, 3, "test"); celix_properties_setVersion(properties, "key", expected); const auto* actual = celix_properties_getVersion(properties, "key", nullptr); EXPECT_EQ(celix_version_getMajor(expected), celix_version_getMajor(actual)); @@ -497,7 +497,7 @@ TEST_F(PropertiesTestSuite, GetVersionTest) { celix_version_destroy(expected); // Test setting without copy - celix_properties_setVersionWithoutCopy(properties, "key3", celix_version_createVersion(3,3,3,"")); + celix_properties_setVersionWithoutCopy(properties, "key3", celix_version_create(3,3,3,"")); actual = celix_properties_getVersion(properties, "key3", emptyVersion); EXPECT_EQ(celix_version_getMajor(actual), 3); EXPECT_EQ(celix_version_getMinor(actual), 3); diff --git a/libs/utils/gtest/src/VersionRangeTestSuite.cc b/libs/utils/gtest/src/VersionRangeTestSuite.cc index 85fa1a7e1..b0dc1ba0a 100644 --- a/libs/utils/gtest/src/VersionRangeTestSuite.cc +++ b/libs/utils/gtest/src/VersionRangeTestSuite.cc @@ -46,7 +46,7 @@ TEST_F(VersionRangeTestSuite, create) { TEST_F(VersionRangeTestSuite, createInfinite) { celix_status_t status = CELIX_SUCCESS; version_range_pt range = nullptr; - version_pt version = celix_version_createVersion(1,2, 3, nullptr); + version_pt version = celix_version_create(1,2, 3, nullptr); status = versionRange_createInfiniteVersionRange(&range); EXPECT_EQ(CELIX_SUCCESS, status); @@ -65,7 +65,7 @@ TEST_F(VersionRangeTestSuite, createInfinite) { TEST_F(VersionRangeTestSuite, isInRange) { bool result; - version_pt version = celix_version_createVersion(1, 2, 3, nullptr); + version_pt version = celix_version_create(1, 2, 3, nullptr); { version_range_pt range = nullptr; diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc index 5707e4272..5e908fd85 100644 --- a/libs/utils/gtest/src/VersionTestSuite.cc +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -44,8 +44,8 @@ TEST_F(VersionTestSuite, CreateTest) { expectVersion(version, 1, 2, 3, "abc"); celix_version_destroy(version); - EXPECT_EQ(nullptr, celix_version_createVersion(-1, -2, -3, "abc")); - EXPECT_EQ(nullptr, celix_version_createVersion(-1, -2, -3, "abc|xyz")); + EXPECT_EQ(nullptr, celix_version_create(-1, -2, -3, "abc")); + EXPECT_EQ(nullptr, celix_version_create(-1, -2, -3, "abc|xyz")); //Testing deprecated api diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 45bbef172..3b6bfec16 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -39,6 +39,7 @@ #include "celix_errno.h" #include "celix_version.h" +#include "celix_utils_export.h" #ifndef CELIX_PROPERTIES_H_ #define CELIX_PROPERTIES_H_ @@ -116,14 +117,14 @@ typedef struct celix_properties_iterator { * * @return A new empty property set. */ -celix_properties_t* celix_properties_create(void); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void); /** * @brief Destroy a property set, freeing all associated resources. * * @param[in] properties The property set to destroy. If properties is NULL, this function will do nothing. */ -void celix_properties_destroy(celix_properties_t* properties); +CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t* properties); /** * @brief Load properties from a file. @@ -132,7 +133,7 @@ void celix_properties_destroy(celix_properties_t* properties); * @return A property set containing the properties from the file. * @retval NULL If an error occurred (e.g. file not found). */ -celix_properties_t* celix_properties_load(const char *filename); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char *filename); /** @@ -142,7 +143,7 @@ celix_properties_t* celix_properties_load(const char *filename); * @return A property set containing the properties from the stream. * @retval NULL If an error occurred (e.g. invalid format). */ -celix_properties_t* celix_properties_loadWithStream(FILE *stream); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream); /** * @brief Load properties from a string. @@ -151,7 +152,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE *stream); * @return A property set containing the properties from the string. * @retval NULL If an error occurred (e.g. invalid format). */ -celix_properties_t* celix_properties_loadFromString(const char *input); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input); /** * @brief Store properties to a file. @@ -164,7 +165,7 @@ celix_properties_t* celix_properties_loadFromString(const char *input); * @return CELIX_SUCCESS if the operation was successful, CELIX_FILE_IO_EXCEPTION if there was an error writing to the * file. */ -celix_status_t celix_properties_store(celix_properties_t* properties, const char* file, const char* header); +CELIX_UTILS_EXPORT celix_status_t celix_properties_store(celix_properties_t* properties, const char* file, const char* header); /** * @brief Get the entry for a given key in a property set. @@ -173,7 +174,7 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char * @param[in] key The key to search for. * @return The entry for the given key, or a NULL if the key is not found. */ -celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, const char* key); +CELIX_UTILS_EXPORT celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, const char* key); /** * @brief Get the value of a property. @@ -183,7 +184,7 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr * @param[in] defaultValue The value to return if the property is not set. * @return The value of the property, or the default value if the property is not set. */ -const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); +CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); /** * @brief Get the type of a property value. @@ -192,7 +193,7 @@ const char* celix_properties_get(const celix_properties_t* properties, const cha * @param[in] key The key of the property to get the type of. * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set. */ -celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key); +CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key); /** * @brief Set the value of a property. @@ -201,7 +202,7 @@ celix_properties_value_type_e celix_properties_getType(const celix_properties_t* * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. */ -void celix_properties_set(celix_properties_t* properties, const char* key, const char *value); +CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t* properties, const char* key, const char *value); /** * @brief Set the value of a property without copying the key and value strings. @@ -214,7 +215,7 @@ void celix_properties_set(celix_properties_t* properties, const char* key, const * @param[in] value The value to set the property to. This string will be used directly, so it must not be freed or * modified after calling this function. */ -void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); +CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); /** * @brief Unset a property, removing it from the property set. @@ -222,7 +223,7 @@ void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, * @param[in] properties The property set to modify. * @param[in] key The key of the property to unset. */ -void celix_properties_unset(celix_properties_t* properties, const char *key); +CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char *key); /** * @brief Make a copy of a property set. @@ -230,7 +231,7 @@ void celix_properties_unset(celix_properties_t* properties, const char *key); * @param[in] properties The property set to copy. * @return A copy of the given property set. */ -celix_properties_t* celix_properties_copy(const celix_properties_t* properties); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t* properties); /** * @brief Get the value of a property as a long integer. @@ -243,7 +244,7 @@ celix_properties_t* celix_properties_copy(const celix_properties_t* properties); * the value is not a long integer, or if the value cannot be converted to a long integer. * If the value is a string, it will be converted to a long integer if possible. */ -long celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); +CELIX_UTILS_EXPORT long celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); /** * @brief Set the value of a property to a long integer. @@ -252,7 +253,7 @@ long celix_properties_getAsLong(const celix_properties_t* properties, const char * @param[in] key The key of the property to set. * @param[in] value The long value to set the property to. */ -void celix_properties_setLong(celix_properties_t* properties, const char* key, long value); +CELIX_UTILS_EXPORT void celix_properties_setLong(celix_properties_t* properties, const char* key, long value); /** * @brief Get the value of a property as a boolean. @@ -265,7 +266,7 @@ void celix_properties_setLong(celix_properties_t* properties, const char* key, l * boolean, or if the value cannot be converted to a boolean. If the value is a string, it will be converted * to a boolean if possible. */ -bool celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); +CELIX_UTILS_EXPORT bool celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); /** * @brief Set the value of a property to a boolean. @@ -274,7 +275,7 @@ bool celix_properties_getAsBool(const celix_properties_t* properties, const char * @param[in] key The key of the property to set. * @param[in] val The boolean value to set the property to. */ -void celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); +CELIX_UTILS_EXPORT void celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); /** * @brief Set the value of a property to a double. @@ -283,7 +284,7 @@ void celix_properties_setBool(celix_properties_t* properties, const char* key, b * @param[in] key The key of the property to set. * @param[in] val The double value to set the property to. */ -void celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); +CELIX_UTILS_EXPORT void celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); /** * @brief Get the value of a property as a double. @@ -296,7 +297,7 @@ void celix_properties_setDouble(celix_properties_t* properties, const char* key, * a double, or if the value cannot be converted to a double. If the value is a string, it will be converted * to a double if possible. */ -double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); +CELIX_UTILS_EXPORT double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); /** * @brief Set the value of a property as a Celix version. @@ -307,7 +308,7 @@ double celix_properties_getAsDouble(const celix_properties_t* properties, const * @param[in] key The key of the property to set. * @param[in] version The value to set. The function will make a copy of this object and store it in the property set. */ -void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); +CELIX_UTILS_EXPORT void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); /** * @brief Set the value of a property as a Celix version. @@ -320,7 +321,7 @@ void celix_properties_setVersion(celix_properties_t* properties, const char* key * @param[in] version The value to set. The function will store a reference to this object in the property set and * takes ownership of the provided version. */ -void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); +CELIX_UTILS_EXPORT void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); /** @@ -334,7 +335,7 @@ void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, cons * @return The value of the property if it is a Celix version, or the default value if the property is not set or the * value is not a Celix version. */ -const celix_version_t* celix_properties_getVersion( +CELIX_UTILS_EXPORT const celix_version_t* celix_properties_getVersion( const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue); @@ -356,7 +357,7 @@ const celix_version_t* celix_properties_getVersion( * is not a valid Celix version. * @retval NULL if version cannot be found/converted and the defaultValue is NULL. */ -celix_version_t* celix_properties_getAsVersion( +CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion( const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue); @@ -367,7 +368,7 @@ celix_version_t* celix_properties_getAsVersion( * @param[in] properties The property set to get the size of. * @return The number of properties in the property set. */ -int celix_properties_size(const celix_properties_t* properties); +CELIX_UTILS_EXPORT int celix_properties_size(const celix_properties_t* properties); /** * @brief Construct an iterator pointing to the first entry in the properties object. @@ -375,7 +376,7 @@ int celix_properties_size(const celix_properties_t* properties); * @param[in] properties The properties object to iterate over. * @return The iterator pointing to the first entry in the properties object. */ -celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties); +CELIX_UTILS_EXPORT celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties); /** * @brief Construct an iterator pointing to the past-the-end entry in the properties object. @@ -386,14 +387,14 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro * @param[in] properties The properties object to iterate over. * @return The iterator pointing to the past-the-end entry in the properties object. */ -celix_properties_iterator_t celix_properties_end(const celix_properties_t* properties); +CELIX_UTILS_EXPORT celix_properties_iterator_t celix_properties_end(const celix_properties_t* properties); /** * @brief Advance the iterator to the next entry. * * @param[in, out] iter The iterator. */ -void celix_propertiesIterator_next(celix_properties_iterator_t* iter); +CELIX_UTILS_EXPORT void celix_propertiesIterator_next(celix_properties_iterator_t* iter); /** * @brief Determine whether the iterator is pointing to an end position. @@ -403,7 +404,7 @@ void celix_propertiesIterator_next(celix_properties_iterator_t* iter); * @param[in] iter The iterator. * @return true if the iterator is at an end position, false otherwise. */ -bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); +CELIX_UTILS_EXPORT bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); /** * @brief Determine whether two iterators are equal. @@ -412,7 +413,7 @@ bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); * @param[in] b The second iterator to compare. * @return true if the iterators are equal, false otherwise. */ -bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); +CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); /** * @brief Iterate over the entries in the specified celix_properties_t object. @@ -452,8 +453,7 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const * @param[in] properties The properties object to iterate over. * @return The newly constructed iterator. */ -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties) - __attribute__((deprecated("celix_propertiesIterator_construct is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); +CELIX_UTILS_DEPRECATED_EXPORT celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties); /** * @brief Determines whether the iterator has more entries. @@ -462,8 +462,7 @@ celix_properties_iterator_t celix_propertiesIterator_construct(const celix_prope * @param[in] iter The iterator. * @return true if the iterator has more entries, false otherwise. */ -bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) - __attribute__((deprecated("celix_propertiesIterator_hasNext is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); +CELIX_UTILS_DEPRECATED_EXPORT bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); /** * @brief Advances the iterator to the next entry and returns the key for the current entry. @@ -474,8 +473,7 @@ bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) * @param[in, out] iter The iterator. * @return The key for the current entry, or NULL if the iterator has no more entries. */ -const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter) - __attribute__((deprecated("celix_propertiesIterator_nextKey is deprecated use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead"))); +CELIX_UTILS_DEPRECATED_EXPORT const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter); /** * @brief Macro for iterating over the properties in a property set. diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index 5c5b03a26..71d6e37d0 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -20,14 +20,15 @@ #ifndef CELIX_CELIX_VERSION_H #define CELIX_CELIX_VERSION_H -#ifdef __cplusplus -extern "C" { -#endif - #include +#include #include "celix_utils_export.h" +#ifdef __cplusplus +extern "C" { +#endif + /** * @file celix_version.h * @brief Header file for the Celix Version API. @@ -54,7 +55,12 @@ typedef struct celix_version celix_version_t; * the empty string. * @return The created version or NULL if the input was incorrect */ -CELIX_UTILS_EXPORT celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier); +CELIX_UTILS_EXPORT celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier); + +/** + * @deprecated Use celix_version_create instead. + */ +CELIX_UTILS_DEPRECATED_EXPORT celix_version_t* celixversion_createVersion(int major, int minor, int micro, const char* qualifier); CELIX_UTILS_EXPORT void celix_version_destroy(celix_version_t* version); diff --git a/libs/utils/private/test/version_ei_test.cc b/libs/utils/private/test/version_ei_test.cc index 66606ae45..3bfd66e66 100644 --- a/libs/utils/private/test/version_ei_test.cc +++ b/libs/utils/private/test/version_ei_test.cc @@ -27,6 +27,8 @@ #include "celix_version.h" +//TODO move to gtest + int main(int argc, char** argv) { MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); return RUN_ALL_TESTS(argc, argv); @@ -47,10 +49,10 @@ TEST_GROUP(version_ei) { TEST(version_ei, create) { celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); - celix_version_t *version = celix_version_createVersion(2, 2, 0, nullptr); + celix_version_t *version = celix_version_create(2, 2, 0, nullptr); POINTERS_EQUAL(nullptr, version); celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); - version = celix_version_createVersion(2, 2, 0, nullptr); + version = celix_version_create(2, 2, 0, nullptr); POINTERS_EQUAL(nullptr, version); } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index d0cae6843..76a0e2661 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -570,8 +570,8 @@ celix_status_t celix_properties_store(celix_properties_t *properties, const char } int rc = 0; - CELIX_STRING_HASH_MAP_ITERATE(properties->map, iter) { - const char* val = iter.value.ptrValue; + CELIX_PROPERTIES_ITERATE(properties, iter) { + const char* val = iter.entry.value; if (rc != EOF) { rc = celix_properties_storeEscapedString(file, iter.key); } diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index f7317633a..814caccd9 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -30,7 +30,7 @@ static const char* const CELIX_VERSION_EMPTY_QUALIFIER = ""; celix_status_t version_createVersion(int major, int minor, int micro, const char * qualifier, version_pt *version) { - *version = celix_version_createVersion(major, minor, micro, qualifier); + *version = celix_version_create(major, minor, micro, qualifier); return *version == NULL ? CELIX_ILLEGAL_ARGUMENT : CELIX_SUCCESS; } @@ -97,7 +97,7 @@ celix_status_t version_isCompatible(version_pt user, version_pt provider, bool* return CELIX_SUCCESS; } -celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier) { +celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char * qualifier) { return celix_version_create(major, minor, micro, qualifier); } @@ -156,7 +156,7 @@ celix_version_t* celix_version_copy(const celix_version_t* version) { if (version == NULL) { return celix_version_createEmptyVersion(); } - return celix_version_createVersion(version->major, version->minor, version->micro, version->qualifier); + return celix_version_create(version->major, version->minor, version->micro, version->qualifier); } @@ -228,7 +228,7 @@ celix_version_t* celix_version_createVersionFromString(const char *versionStr) { celix_version_t* version = NULL; if (status == CELIX_SUCCESS) { - version = celix_version_createVersion(major, minor, micro, qualifier); + version = celix_version_create(major, minor, micro, qualifier); } if (qualifier != NULL) { @@ -240,7 +240,7 @@ celix_version_t* celix_version_createVersionFromString(const char *versionStr) { celix_version_t* celix_version_createEmptyVersion() { - return celix_version_createVersion(0, 0, 0, NULL); + return celix_version_create(0, 0, 0, NULL); } int celix_version_getMajor(const celix_version_t* version) { From 78b94d588e4e96b295b422fbb03a224e25f34ffe Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 7 May 2023 19:20:37 +0200 Subject: [PATCH 15/63] Refactor cpputest version ei test to gtest --- libs/utils/CMakeLists.txt | 7 ------ libs/utils/gtest/CMakeLists.txt | 1 + .../src/VersionErrorInjectionTestSuite.cc} | 24 +++++++------------ 3 files changed, 10 insertions(+), 22 deletions(-) rename libs/utils/{private/test/version_ei_test.cc => gtest/src/VersionErrorInjectionTestSuite.cc} (79%) diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index df8909a08..9fad8b863 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -147,13 +147,6 @@ if (ENABLE_TESTING) target_include_directories(ip_utils_test PRIVATE include_deprecated) target_link_libraries(ip_utils_test CppUTest::CppUTest utils_cut pthread) - if (LINKER_WRAP_SUPPORTED) - add_executable(version_ei_test private/test/version_ei_test.cc) - target_include_directories(version_ei_test PRIVATE include_deprecated) - target_link_libraries(version_ei_test CppUTest::CppUTest utils_cut Celix::malloc_ei Celix::utils_ei pthread) - add_test(NAME version_ei_test COMMAND version_ei_test) - endif () - add_test(NAME run_array_list_test COMMAND array_list_test) add_test(NAME run_hash_map_test COMMAND hash_map_test) add_test(NAME run_linked_list_test COMMAND linked_list_test) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 8fc7264b5..8c5b37c09 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -93,6 +93,7 @@ if (LINKER_WRAP_SUPPORTED) src/IpUtilsErrorInjectionTestSuite.cc src/ArrayListErrorInjectionTestSuite.cc src/ErrErrorInjectionTestSuite.cc + src/VersionErrorInjectionTestSuite.cc ) target_link_libraries(test_utils_with_ei PRIVATE Celix::zip_ei Celix::stdio_ei Celix::stat_ei Celix::fts_ei utils_cut Celix::utils_ei Celix::ifaddrs_ei Celix::threads_ei Celix::malloc_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/private/test/version_ei_test.cc b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc similarity index 79% rename from libs/utils/private/test/version_ei_test.cc rename to libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc index 3bfd66e66..d59da470b 100644 --- a/libs/utils/private/test/version_ei_test.cc +++ b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc @@ -17,6 +17,7 @@ under the License. */ +#include #include #include "celix_utils_ei.h" @@ -27,32 +28,25 @@ #include "celix_version.h" -//TODO move to gtest -int main(int argc, char** argv) { - MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); - return RUN_ALL_TESTS(argc, argv); -} - -TEST_GROUP(version_ei) { - - void setup(void) { +class VersionErrorInjectionTestSuite : public ::testing::Test { +public: + VersionErrorInjectionTestSuite() { celix_ei_expect_calloc(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } - - void teardown(void) { + ~VersionErrorInjectionTestSuite() noexcept override { celix_ei_expect_calloc(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } }; -TEST(version_ei, create) { +TEST_F(VersionErrorInjectionTestSuite, CreateTest) { celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); celix_version_t *version = celix_version_create(2, 2, 0, nullptr); - POINTERS_EQUAL(nullptr, version); + EXPECT_EQ(nullptr, version); celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); - version = celix_version_create(2, 2, 0, nullptr); - POINTERS_EQUAL(nullptr, version); + version = celix_version_create(2, 2, 0, "qualifier"); + EXPECT_EQ(nullptr, version); } From 717869d6aa8dcc08bb6967e43db57bc23fff8e2b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 7 May 2023 23:55:28 +0200 Subject: [PATCH 16/63] Add missing output param in serviceReference_getPropertyKeys --- libs/framework/src/service_reference.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/framework/src/service_reference.c b/libs/framework/src/service_reference.c index c207b0486..4bc9b73ca 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -206,7 +206,8 @@ celix_status_t serviceReference_getPropertyKeys(service_reference_pt ref, char * return status; } - *keys = malloc(celix_properties_size(props) * sizeof(**keys)); + *size = celix_properties_size(props); + *keys = malloc((*size) * sizeof(**keys)); if (!*keys) { return ENOMEM; } From e0326790ce689f800b964caa21bad355ab0728ae Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Wed, 23 Aug 2023 19:12:36 +0200 Subject: [PATCH 17/63] Refactor and correction after merge with master --- libs/utils/CMakeLists.txt | 12 +----------- .../celix_hash_map/CMakeLists.txt | 1 + .../celix_hash_map/include/celix_hash_map_ei.h | 2 ++ .../celix_hash_map/src/celix_hash_map_ei.cc | 9 +++++++++ libs/utils/gtest/CMakeLists.txt | 3 +-- .../src/PropertiesErrorInjectionTestSuite.cc | 16 ++++++++++++---- libs/utils/gtest/src/PropertiesTestSuite.cc | 4 ++++ libs/utils/include/celix_utils.h | 9 --------- libs/utils/src/properties.c | 4 ++++ libs/utils/src/utils.c | 18 +++++++++--------- 10 files changed, 43 insertions(+), 35 deletions(-) diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index f902e8255..4f12f604b 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -159,20 +159,10 @@ if (UTILS) add_test(NAME run_linked_list_test COMMAND linked_list_test) add_test(NAME run_ip_utils_test COMMAND ip_utils_test) - if (LINKER_WRAP_SUPPORTED) - add_executable(version_ei_test private/test/version_ei_test.cc) - target_include_directories(version_ei_test PRIVATE include_deprecated) - target_link_libraries(version_ei_test CppUTest::CppUTest utils_cut Celix::malloc_ei Celix::utils_ei pthread) - add_test(NAME version_ei_test COMMAND version_ei_test) - setup_target_for_coverage(version_ei_test) - endif () - - setup_target_for_coverage(array_list_test) setup_target_for_coverage(hash_map_test) + setup_target_for_coverage(array_list_test) setup_target_for_coverage(linked_list_test) - setup_target_for_coverage(properties_test) setup_target_for_coverage(ip_utils_test) - setup_target_for_coverage(version_test) else () message(WARNING "Cannot find CppUTest, deprecated cpputest-based unit test will not be added") endif () #end CppUTest_FOUND diff --git a/libs/utils/error_injector/celix_hash_map/CMakeLists.txt b/libs/utils/error_injector/celix_hash_map/CMakeLists.txt index 25f87d49e..19df3d4ac 100644 --- a/libs/utils/error_injector/celix_hash_map/CMakeLists.txt +++ b/libs/utils/error_injector/celix_hash_map/CMakeLists.txt @@ -21,5 +21,6 @@ target_include_directories(hash_map_ei PUBLIC include) target_link_libraries(hash_map_ei PUBLIC Celix::error_injector Celix::utils) target_link_options(hash_map_ei INTERFACE LINKER:--wrap,celix_stringHashMap_create + LINKER:--wrap,celix_stringHashMap_createWithOptions ) add_library(Celix::hash_map_ei ALIAS hash_map_ei) diff --git a/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h b/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h index 3c38ad419..5dbdd658d 100644 --- a/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h +++ b/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h @@ -28,6 +28,8 @@ extern "C" { CELIX_EI_DECLARE(celix_stringHashMap_create, celix_string_hash_map_t*); +CELIX_EI_DECLARE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); + #ifdef __cplusplus } #endif diff --git a/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc b/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc index 4edbc3810..bbd3af6da 100644 --- a/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc +++ b/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc @@ -20,10 +20,19 @@ #include "celix_hash_map_ei.h" extern "C" { + celix_string_hash_map_t* __real_celix_stringHashMap_create(); CELIX_EI_DEFINE(celix_stringHashMap_create, celix_string_hash_map_t*); celix_string_hash_map_t* __wrap_celix_stringHashMap_create() { CELIX_EI_IMPL(celix_stringHashMap_create); return __real_celix_stringHashMap_create(); } + +celix_string_hash_map_t* __real_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts); +CELIX_EI_DEFINE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); +celix_string_hash_map_t* __wrap_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts) { + CELIX_EI_IMPL(celix_stringHashMap_createWithOptions); + return __real_celix_stringHashMap_createWithOptions(opts); +} + } diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 0dd222d6e..89786deec 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -108,7 +108,7 @@ if (LINKER_WRAP_SUPPORTED) Celix::ifaddrs_ei Celix::threads_ei Celix::malloc_ei - Celix::hmap_ei + Celix::hash_map_ei GTest::gtest GTest::gtest_main ) target_include_directories(test_utils_with_ei PRIVATE ../src) #for version_private (needs refactoring of test) @@ -130,4 +130,3 @@ if (ENABLE_TESTING_FOR_CXX14) add_test(NAME test_utils_cxx_headers_with_cxx14 COMMAND test_utils_cxx_headers_with_cxx14) setup_target_for_coverage(test_utils_cxx_headers_with_cxx14 SCAN_DIR ..) endif () - diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 13f1f6b65..89aa2e692 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -18,7 +18,9 @@ * */ #include "celix_properties.h" -#include "hmap_ei.h" + +#include "malloc_ei.h" +#include "celix_hash_map_ei.h" #include @@ -26,13 +28,19 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: PropertiesErrorInjectionTestSuite() = default; ~PropertiesErrorInjectionTestSuite() override { - celix_ei_expect_hashMap_create(nullptr, 0, nullptr); + celix_ei_expect_malloc(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); } }; +TEST_F(PropertiesErrorInjectionTestSuite, CreateFailureTest) { + celix_ei_expect_malloc((void *)celix_properties_create, 0, nullptr); + ASSERT_EQ(nullptr, celix_properties_create()); +} + TEST_F(PropertiesErrorInjectionTestSuite, CopyFailureTest) { celix_autoptr(celix_properties_t) prop = celix_properties_create(); ASSERT_NE(nullptr, prop); - celix_ei_expect_hashMap_create((void *) &celix_properties_create, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions((void *)celix_properties_create, 0, nullptr); ASSERT_EQ(nullptr, celix_properties_copy(prop)); -} \ No newline at end of file +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 5317af76a..40a36f59e 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -585,3 +585,7 @@ TEST_F(PropertiesTestSuite, DeprecatedApiTest) { properties_destroy(props); } + +TEST_F(PropertiesTestSuite, PropertiesAutoCleanupTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); +} diff --git a/libs/utils/include/celix_utils.h b/libs/utils/include/celix_utils.h index b3969700a..30168409e 100644 --- a/libs/utils/include/celix_utils.h +++ b/libs/utils/include/celix_utils.h @@ -117,15 +117,6 @@ CELIX_UTILS_EXPORT char* celix_utils_trim(const char* string); */ CELIX_UTILS_EXPORT char* celix_utils_trimInPlace(char* string); -/** - * @brief Trims a string in place. - * - * The trim will remove any leading and trailing whitespaces (' ', '\t', etc based on isspace) from the input string. - * The input string is modified in place. - * @param[in,out] string The string to trim. - */ -void celix_utils_trimInPlace(char* string); - /** * @brief Check if a string is NULL or empty "". */ diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 116608891..6b208c3ab 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -359,6 +359,10 @@ celix_properties_t* celix_properties_create(void) { props->map = celix_stringHashMap_createWithOptions(&opts); props->currentStringBufferIndex = 0; props->currentEntriesBufferIndex = 0; + if (props->map == NULL) { + free(props); + props = NULL; + } } return props; } diff --git a/libs/utils/src/utils.c b/libs/utils/src/utils.c index 01c91b00f..36919e0d3 100644 --- a/libs/utils/src/utils.c +++ b/libs/utils/src/utils.c @@ -116,12 +116,12 @@ char * string_ndup(const char *s, size_t n) { return ret; } -void celix_utils_trimInPlace(char* string) { +static char* celix_utilsTrimInternal(char* string) { if (string == NULL) { - return; + return NULL; } - char* begin = string; //save begin to check in the end. + char* begin = string; //save begin to correctly free in the end. char *end; // Trim leading space @@ -137,7 +137,7 @@ void celix_utils_trimInPlace(char* string) { } if (string != begin) { - //beginning whitespaces -> move chars to the beginning of string + //beginning whitespaces -> move char in copy to to begin string //This to ensure free still works on the same pointer. char* nstring = begin; while(*string != '\0') { @@ -145,20 +145,20 @@ void celix_utils_trimInPlace(char* string) { } (*nstring) = '\0'; } + + return begin; } char* celix_utils_trim(const char* string) { - char* result = celix_utils_strdup(string); - celix_utils_trimInPlace(result); - return result; + return celix_utilsTrimInternal(celix_utils_strdup(string)); } + char* celix_utils_trimInPlace(char* string) { return celix_utilsTrimInternal(string); } char* utils_stringTrim(char* string) { - celix_utils_trimInPlace(string); - return string; + return celix_utilsTrimInternal(string); } bool utils_isStringEmptyOrNull(const char * const str) { From 226a546a2d48173960cc553c0d06b2b1390d45c6 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Wed, 23 Aug 2023 20:35:35 +0200 Subject: [PATCH 18/63] Remove cpputest include from gtest --- libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc | 5 +++-- libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc | 5 ----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 89aa2e692..270e4ec38 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -17,13 +17,14 @@ * under the License. * */ + +#include + #include "celix_properties.h" #include "malloc_ei.h" #include "celix_hash_map_ei.h" -#include - class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: PropertiesErrorInjectionTestSuite() = default; diff --git a/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc index d59da470b..cf570c6ca 100644 --- a/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc @@ -18,14 +18,9 @@ */ #include -#include #include "celix_utils_ei.h" -#include "CppUTest/TestHarness.h" -#include "CppUTest/TestHarness_c.h" -#include "CppUTest/CommandLineTestRunner.h" #include "malloc_ei.h" - #include "celix_version.h" From 09882913b182edfa05dbe908ea936328c4bc8599 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 24 Aug 2023 14:05:21 +0200 Subject: [PATCH 19/63] Fix properties iterator usage in disc zeroconf --- .../discovery_zeroconf/src/discovery_zeroconf_announcer.c | 7 +++---- libs/utils/include/celix_properties.h | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c index c79d1d1ee..40c38be76 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c @@ -342,11 +342,10 @@ static void discoveryZeroconfAnnouncer_revokeEndpoints(discovery_zeroconf_announ static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroconf_announcer_t *announcer, celix_properties_iterator_t *propIter, TXTRecordRef *txtRecord, uint16_t maxTxtLen, bool splitTxtRecord) { const char *key; const char *val; - celix_properties_t *props; while (celix_propertiesIterator_hasNext(propIter)) { - key = celix_propertiesIterator_nextKey(propIter); - props = celix_propertiesIterator_properties(propIter); - val = celix_properties_get(props, key, ""); + celix_propertiesIterator_next(propIter); + key = propIter->key; + val = propIter->entry.value; if (key) { DNSServiceErrorType err = TXTRecordSetValue(txtRecord, key, strlen(val), val); if (err != kDNSServiceErr_NoError) { diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 69efa33e7..c839473ac 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -426,7 +426,7 @@ CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_i * The loop variable `iterName` will be of type celix_properties_iterator_t and will contain the current * entry during each iteration. * - * @param[in] map The properties object to iterate over. + * @param[in] props The properties object to iterate over. * @param[in] iterName The name of the iterator variable to use in the loop. * * Example usage: @@ -438,8 +438,8 @@ CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_i * } * @endcode */ -#define CELIX_PROPERTIES_ITERATE(map, iterName) \ - for (celix_properties_iterator_t iterName = celix_properties_begin(map); \ +#define CELIX_PROPERTIES_ITERATE(props, iterName) \ + for (celix_properties_iterator_t iterName = celix_properties_begin((props)); \ !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) From 1f5bf7907294122db30c7fb5c5ed6be617d61fe5 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 1 Oct 2023 18:49:11 +0200 Subject: [PATCH 20/63] #509: Update changes for celix properties with type support --- CHANGES.md | 10 ++++++++++ libs/utils/gtest/src/VersionRangeTestSuite.cc | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 26e98b052..54a0ccdc8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,16 @@ See the License for the specific language governing permissions and limitations under the License. --> +# Noteworthy Changes for 3.0.0 (TBD) + +## Backwards incompatible changes + +- C Properties are no longer a direct typedef o `hashmap`. + +## New Features + +- Minimal form of type support for celix Properties. + # Noteworthy Changes for 2.4.0 (2023-09-27) ## New Features diff --git a/libs/utils/gtest/src/VersionRangeTestSuite.cc b/libs/utils/gtest/src/VersionRangeTestSuite.cc index 663b7bc10..2c44a79fb 100644 --- a/libs/utils/gtest/src/VersionRangeTestSuite.cc +++ b/libs/utils/gtest/src/VersionRangeTestSuite.cc @@ -418,8 +418,8 @@ TEST_F(VersionRangeTestSuite, createLdapFilterInPlaceInfiniteHigh) { } TEST_F(VersionRangeTestSuite, createLdapFilterWithQualifier) { - celix_autoptr(celix_version_t) low = celix_version_createVersion(1, 2, 2, "0"); - celix_autoptr(celix_version_t) high = celix_version_createVersion(1, 2, 2, "0"); + celix_autoptr(celix_version_t) low = celix_version_create(1, 2, 2, "0"); + celix_autoptr(celix_version_t) high = celix_version_create(1, 2, 2, "0"); celix_autoptr(celix_version_range_t) range = celix_versionRange_createVersionRange(celix_steal_ptr(low), true, celix_steal_ptr(high), true); From 77f212aecbf885f1f79d6d2ae386875402ea4a31 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 7 Oct 2023 19:24:23 +0200 Subject: [PATCH 21/63] #509: Remove deprecated macro (incl usage) CELIX_PROPERTIES_FOR_EACH --- .../src/pubsub_wire_protocol_common.c | 8 +- libs/framework/gtest/src/ManifestTestSuite.cc | 5 +- libs/framework/include/celix/Trackers.h | 2 +- .../include/celix/UseServiceBuilder.h | 4 +- .../include/celix/dm/DependencyManager_Impl.h | 6 +- .../include/celix/dm/ServiceDependency_Impl.h | 32 +------- libs/framework/src/celix_launcher.c | 32 ++++---- libs/framework/src/manifest.c | 5 +- libs/rcm/src/celix_capability.c | 43 ++++------ libs/rcm/src/celix_requirement.c | 56 +++++-------- .../utils/gtest/src/CxxPropertiesTestSuite.cc | 19 ++++- libs/utils/gtest/src/PropertiesTestSuite.cc | 51 ++++++------ libs/utils/include/celix/Properties.h | 19 ++++- libs/utils/include/celix_properties.h | 80 +++---------------- libs/utils/src/properties.c | 66 ++++++--------- 15 files changed, 161 insertions(+), 267 deletions(-) diff --git a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c index 7b78cea07..88a9bbff5 100644 --- a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c +++ b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c @@ -101,19 +101,17 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t* message, *bufferInOut = newBuffer; *bufferLengthInOut = newLength; } - const char* key; if (metadataSize == 0) { encoded = true; continue; } celix_status_t status = CELIX_SUCCESS; - CELIX_PROPERTIES_FOR_EACH(message->metadata.metadata, key) { - const char *val = celix_properties_get(message->metadata.metadata, key, ""); + CELIX_PROPERTIES_ITERATE(message->metadata.metadata, iter) { if (status == CELIX_SUCCESS) { - status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, key); + status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, iter.key); } if (status == CELIX_SUCCESS) { - status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, val); + status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, iter.entry.value); } } if (status == CELIX_FILE_IO_EXCEPTION) { diff --git a/libs/framework/gtest/src/ManifestTestSuite.cc b/libs/framework/gtest/src/ManifestTestSuite.cc index 51cb2bf63..6cb668353 100644 --- a/libs/framework/gtest/src/ManifestTestSuite.cc +++ b/libs/framework/gtest/src/ManifestTestSuite.cc @@ -42,9 +42,8 @@ class ManifestTestSuite : public ::testing::Test { } void CheckPropertiesEqual(const celix_properties_t* prop1, const celix_properties_t* prop2) { EXPECT_EQ(celix_properties_size(prop1), celix_properties_size(prop2)); - const char* key = nullptr; - CELIX_PROPERTIES_FOR_EACH(prop1, key) { - EXPECT_STREQ(celix_properties_get(prop1, key, nullptr), celix_properties_get(prop2, key, nullptr)); + CELIX_PROPERTIES_ITERATE(prop1, iter) { + EXPECT_STREQ(celix_properties_get(prop1, iter.key, nullptr), celix_properties_get(prop2, iter.key, nullptr)); } } void CheckManifestEqual(const manifest_pt manifest1, const manifest_pt manifest2) { diff --git a/libs/framework/include/celix/Trackers.h b/libs/framework/include/celix/Trackers.h index 5498607ca..f702c958e 100644 --- a/libs/framework/include/celix/Trackers.h +++ b/libs/framework/include/celix/Trackers.h @@ -447,7 +447,7 @@ namespace celix { long svcId = celix_properties_getAsLong(cProps, OSGI_FRAMEWORK_SERVICE_ID, -1L); long svcRanking = celix_properties_getAsLong(cProps, OSGI_FRAMEWORK_SERVICE_RANKING, 0); auto svc = std::shared_ptr{static_cast(voidSvc), [](I*){/*nop*/}}; - auto props = celix::Properties::wrap(cProps); + auto props = std::make_shared(celix::Properties::wrap(cProps)); auto owner = std::make_shared(const_cast(cBnd)); return std::make_shared(svcId, svcRanking, svc, props, owner); } diff --git a/libs/framework/include/celix/UseServiceBuilder.h b/libs/framework/include/celix/UseServiceBuilder.h index 1e86cee6c..5f37f95af 100644 --- a/libs/framework/include/celix/UseServiceBuilder.h +++ b/libs/framework/include/celix/UseServiceBuilder.h @@ -164,10 +164,10 @@ namespace celix { func(*svc); } for (const auto& func : builder->callbacksWithProperties) { - func(*svc, *props); + func(*svc, props); } for (const auto& func : builder->callbacksWithOwner) { - func(*svc, *props, bnd); + func(*svc, props, bnd); } }; diff --git a/libs/framework/include/celix/dm/DependencyManager_Impl.h b/libs/framework/include/celix/dm/DependencyManager_Impl.h index 76157a4a6..e364d9f10 100644 --- a/libs/framework/include/celix/dm/DependencyManager_Impl.h +++ b/libs/framework/include/celix/dm/DependencyManager_Impl.h @@ -174,10 +174,8 @@ static celix::dm::DependencyManagerInfo createDepManInfoFromC(celix_dependency_m auto* cIntInfo = static_cast(celix_arrayList_get(cCmpInfo->interfaces, k)); celix::dm::InterfaceInfo intInfo{}; intInfo.serviceName = std::string{cIntInfo->name}; - const char* key; - CELIX_PROPERTIES_FOR_EACH(cIntInfo->properties, key) { - const char* val =celix_properties_get(cIntInfo->properties, key, ""); - intInfo.properties[std::string{key}] = std::string{val}; + CELIX_PROPERTIES_ITERATE(cIntInfo->properties, iter) { + intInfo.properties[std::string{iter.key}] = std::string{iter.entry.value}; } cmpInfo.interfacesInfo.emplace_back(std::move(intInfo)); } diff --git a/libs/framework/include/celix/dm/ServiceDependency_Impl.h b/libs/framework/include/celix/dm/ServiceDependency_Impl.h index 8fc501045..566ad53ed 100644 --- a/libs/framework/include/celix/dm/ServiceDependency_Impl.h +++ b/libs/framework/include/celix/dm/ServiceDependency_Impl.h @@ -237,20 +237,8 @@ void CServiceDependency::setupCallbacks() { template int CServiceDependency::invokeCallback(std::function fp, const celix_properties_t *props, const void* service) { - Properties properties {}; - const char* key {nullptr}; - const char* value {nullptr}; - - if (props != nullptr) { - CELIX_PROPERTIES_FOR_EACH(props, key) { - value = celix_properties_get(props, key, ""); //note. C++ does not allow nullptr entries for std::string - //std::cout << "got property " << key << "=" << value << "\n"; - properties[key] = value; - } - } - + auto properties = Properties::copy(props); const I* srv = (const I*) service; - fp(srv, std::move(properties)); return 0; } @@ -504,19 +492,7 @@ ServiceDependency& ServiceDependency::setStrategy(DependencyUpdateStra template int ServiceDependency::invokeCallback(std::function fp, const celix_properties_t *props, const void* service) { I *svc = (I*)service; - - Properties properties {}; - const char* key {nullptr}; - const char* value {nullptr}; - - if (props != nullptr) { - CELIX_PROPERTIES_FOR_EACH(props, key) { - value = celix_properties_get(props, key, ""); - //std::cout << "got property " << key << "=" << value << "\n"; - properties[key] = value; - } - } - + auto properties = celix::Properties::wrap(props); fp(svc, std::move(properties)); //explicit move of lvalue properties. return 0; } @@ -539,7 +515,7 @@ void ServiceDependency::setupCallbacks() { std::weak_ptr replacedSvc = dep->setService.first; std::weak_ptr replacedProps = dep->setService.second; auto svc = std::shared_ptr{static_cast(rawSvc), [](I*){/*nop*/}}; - auto props = rawProps ? celix::Properties::wrap(rawProps) : nullptr; + auto props = rawProps ? std::make_shared(celix::Properties::wrap(rawProps)) : nullptr; dep->setService = std::make_pair(std::move(svc), std::move(props)); dep->setFpUsingSharedPtr(dep->setService.first, dep->setService.second); dep->waitForExpired(replacedSvc, svcId, "service pointer"); @@ -556,7 +532,7 @@ void ServiceDependency::setupCallbacks() { rc = dep->invokeCallback(dep->addFp, rawProps, rawSvc); } if (dep->addFpUsingSharedPtr) { - auto props = celix::Properties::wrap(rawProps); + auto props = std::make_shared(celix::Properties::wrap(rawProps)); auto svc = std::shared_ptr{static_cast(rawSvc), [](I*){/*nop*/}}; auto svcId = props->getAsLong(celix::SERVICE_ID, -1); dep->addFpUsingSharedPtr(svc, props); diff --git a/libs/framework/src/celix_launcher.c b/libs/framework/src/celix_launcher.c index ca0b1d1b3..532e2c20f 100644 --- a/libs/framework/src/celix_launcher.c +++ b/libs/framework/src/celix_launcher.c @@ -193,17 +193,15 @@ static void celixLauncher_printUsage(char* progName) { } static void celixLauncher_printProperties(celix_properties_t *embeddedProps, const char *configFile) { - const char *key = NULL; celix_properties_t *keys = celix_properties_create(); //only to store the keys printf("Embedded properties:\n"); if (embeddedProps == NULL || celix_properties_size(embeddedProps) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(embeddedProps, key) { - const char *val = celix_properties_get(embeddedProps, key, "!Error!"); - printf("|- %s=%s\n", key, val); - celix_properties_set(keys, key, NULL); + CELIX_PROPERTIES_ITERATE(embeddedProps, visit) { + printf("|- %s=%s\n", visit.key, visit.entry.value); + celix_properties_set(keys, visit.key, NULL); } } printf("\n"); @@ -216,11 +214,10 @@ static void celixLauncher_printProperties(celix_properties_t *embeddedProps, con if (runtimeProps == NULL || celix_properties_size(runtimeProps) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(runtimeProps, key) { - const char *val = celix_properties_get(runtimeProps, key, "!Error!"); - printf("|- %s=%s\n", key, val); - celix_properties_set(keys, key, NULL); - } + CELIX_PROPERTIES_ITERATE(runtimeProps, visit) { + printf("|- %s=%s\n", visit.key, visit.entry.value); + celix_properties_set(keys, visit.key, NULL); + } } printf("\n"); @@ -229,13 +226,13 @@ static void celixLauncher_printProperties(celix_properties_t *embeddedProps, con if (celix_properties_size(keys) == 0) { printf("|- Empty!\n"); } else { - CELIX_PROPERTIES_FOR_EACH(keys, key) { - const char *valEm = celix_properties_get(embeddedProps, key, NULL); - const char *valRt = celix_properties_get(runtimeProps, key, NULL); - const char *envVal = getenv(key); + CELIX_PROPERTIES_ITERATE(keys, visit) { + const char *valEm = celix_properties_get(embeddedProps, visit.key, NULL); + const char *valRt = celix_properties_get(runtimeProps, visit.key, NULL); + const char *envVal = getenv(visit.key); const char *val = envVal != NULL ? envVal : valRt != NULL ? valRt : valEm; const char *source = envVal != NULL ? "environment" : valRt != NULL ? "runtime" : "embedded"; - printf("|- %s=%s (source %s)\n", key, val, source); + printf("|- %s=%s (source %s)\n", visit.key, val, source); } } printf("\n"); @@ -279,9 +276,8 @@ static int celixLauncher_createBundleCache(celix_properties_t* embeddedPropertie static void celixLauncher_combineProperties(celix_properties_t *original, const celix_properties_t *append) { if (original != NULL && append != NULL) { - const char *key = NULL; - CELIX_PROPERTIES_FOR_EACH(append, key) { - celix_properties_set(original, key, celix_properties_get(append, key, NULL)); + CELIX_PROPERTIES_ITERATE(append, visit) { + celix_properties_setEntry(original, visit.key, &visit.entry); } } } diff --git a/libs/framework/src/manifest.c b/libs/framework/src/manifest.c index 4f0884e90..1ba4f9abf 100644 --- a/libs/framework/src/manifest.c +++ b/libs/framework/src/manifest.c @@ -72,9 +72,8 @@ manifest_pt manifest_clone(manifest_pt manifest) { celix_auto(manifest_pt) clone = NULL; status = manifest_create(&clone); if (status == CELIX_SUCCESS) { - const char* key = NULL; - CELIX_PROPERTIES_FOR_EACH(manifest->mainAttributes, key) { - celix_properties_set(clone->mainAttributes, key, celix_properties_get(manifest->mainAttributes, key, NULL)); + CELIX_PROPERTIES_ITERATE(manifest->mainAttributes, visit) { + celix_properties_set(clone->mainAttributes, visit.key, visit.entry.value); } hash_map_iterator_t iter = hashMapIterator_construct(manifest->attributes); while (hashMapIterator_hasNext(&iter)) { diff --git a/libs/rcm/src/celix_capability.c b/libs/rcm/src/celix_capability.c index 93aeaf5b2..35be9a717 100644 --- a/libs/rcm/src/celix_capability.c +++ b/libs/rcm/src/celix_capability.c @@ -83,11 +83,9 @@ bool celix_capability_equals(const celix_capability_t* cap1, const celix_capabil //compare attributes bool equals = true; - const char* visit; - CELIX_PROPERTIES_FOR_EACH(cap1->attributes, visit) { - const char* value1 = celix_properties_get(cap1->attributes, visit, NULL); - const char* value2 = celix_properties_get(cap2->attributes, visit, NULL); - if (!celix_utils_stringEquals(value1, value2)) { + CELIX_PROPERTIES_ITERATE(cap1->attributes, visit) { + const char* value2 = celix_properties_get(cap2->attributes, visit.key, NULL); + if (!celix_utils_stringEquals(visit.entry.value, value2)) { equals = false; break; } @@ -95,10 +93,9 @@ bool celix_capability_equals(const celix_capability_t* cap1, const celix_capabil if (!equals) { return false; } - CELIX_PROPERTIES_FOR_EACH(cap1->directives, visit) { - const char* value1 = celix_properties_get(cap1->directives, visit, NULL); - const char* value2 = celix_properties_get(cap2->directives, visit, NULL); - if (!celix_utils_stringEquals(value1, value2)) { + CELIX_PROPERTIES_ITERATE(cap1->directives, visit) { + const char* value2 = celix_properties_get(cap2->directives, visit.key, NULL); + if (!celix_utils_stringEquals(visit.entry.value, value2)) { equals = false; break; } @@ -108,17 +105,13 @@ bool celix_capability_equals(const celix_capability_t* cap1, const celix_capabil unsigned int celix_capability_hashCode(const celix_capability_t* cap) { unsigned int hash = celix_utils_stringHash(cap->ns); - const char* visit; - - CELIX_PROPERTIES_FOR_EACH(cap->attributes, visit) { - const char* value = celix_properties_get(cap->attributes, visit, NULL); - hash += celix_utils_stringHash(visit); - hash += celix_utils_stringHash(value); + CELIX_PROPERTIES_ITERATE(cap->attributes, visit) { + hash += celix_utils_stringHash(visit.key); + hash += celix_utils_stringHash(visit.entry.value); } - CELIX_PROPERTIES_FOR_EACH(cap->directives, visit) { - const char* value = celix_properties_get(cap->directives, visit, NULL); - hash += celix_utils_stringHash(visit); - hash += celix_utils_stringHash(value); + CELIX_PROPERTIES_ITERATE(cap->directives, visit) { + hash += celix_utils_stringHash(visit.key); + hash += celix_utils_stringHash(visit.entry.value); } return hash; } @@ -152,10 +145,8 @@ void celix_capability_addAttribute(celix_capability_t* cap, const char* key, con } void celix_capability_addAttributes(celix_capability_t* cap, const celix_properties_t* attributes) { - const char* visit; - CELIX_PROPERTIES_FOR_EACH(attributes, visit) { - const char* value = celix_properties_get(attributes, visit, NULL); - celix_properties_set(cap->attributes, visit, value); + CELIX_PROPERTIES_ITERATE(attributes, visit) { + celix_properties_set(cap->attributes, visit.key, visit.entry.value); } } @@ -164,9 +155,7 @@ void celix_capability_addDirective(celix_capability_t* cap, const char* key, con } void celix_capability_addDirectives(celix_capability_t* cap, const celix_properties_t* directives) { - const char* visit; - CELIX_PROPERTIES_FOR_EACH(directives, visit) { - const char* value = celix_properties_get(directives, visit, NULL); - celix_properties_set(cap->directives, visit, value); + CELIX_PROPERTIES_ITERATE(directives, visit) { + celix_properties_set(cap->directives, visit.key, visit.entry.value); } } diff --git a/libs/rcm/src/celix_requirement.c b/libs/rcm/src/celix_requirement.c index 48f3dd452..a6480ca7c 100644 --- a/libs/rcm/src/celix_requirement.c +++ b/libs/rcm/src/celix_requirement.c @@ -89,45 +89,33 @@ bool celix_requirement_equals(const celix_requirement_t* req1, const celix_requi } //compare attributes - bool equals = true; - const char* visit; - CELIX_PROPERTIES_FOR_EACH(req1->attributes, visit) { - const char* val1 = celix_properties_get(req1->attributes, visit, NULL); - const char* val2 = celix_properties_get(req2->attributes, visit, NULL); - if (!celix_utils_stringEquals(val1, val2)) { - equals = false; - break; + CELIX_PROPERTIES_ITERATE(req1->attributes, visit) { + const char* val2 = celix_properties_get(req2->attributes, visit.key, NULL); + if (!celix_utils_stringEquals(visit.entry.value, val2)) { + return false; } } - if (!equals) { - return false; - } //compare directives - CELIX_PROPERTIES_FOR_EACH(req1->directives, visit) { - const char* val1 = celix_properties_get(req1->directives, visit, NULL); - const char* val2 = celix_properties_get(req2->directives, visit, NULL); - if (!celix_utils_stringEquals(val1, val2)) { - equals = false; - break; + CELIX_PROPERTIES_ITERATE(req1->directives, visit) { + const char* val2 = celix_properties_get(req2->directives, visit.key, NULL); + if (!celix_utils_stringEquals(visit.entry.value, val2)) { + return false; } } - return equals; + + return true; } unsigned int celix_requirement_hashCode(const celix_requirement_t* req) { unsigned int hash = celix_utils_stringHash(req->ns); - const char* visit; - - CELIX_PROPERTIES_FOR_EACH(req->attributes, visit) { - const char* val = celix_properties_get(req->attributes, visit, NULL); - hash += celix_utils_stringHash(visit); - hash += celix_utils_stringHash(val); + CELIX_PROPERTIES_ITERATE(req->attributes, visit) { + hash += celix_utils_stringHash(visit.key); + hash += celix_utils_stringHash(visit.entry.value); } - CELIX_PROPERTIES_FOR_EACH(req->directives, visit) { - const char* val = celix_properties_get(req->directives, visit, NULL); - hash += celix_utils_stringHash(visit); - hash += celix_utils_stringHash(val); + CELIX_PROPERTIES_ITERATE(req->directives, visit) { + hash += celix_utils_stringHash(visit.key); + hash += celix_utils_stringHash(visit.entry.value); } return hash; } @@ -169,17 +157,13 @@ void celix_requirement_addAttribute(celix_requirement_t* req, const char* key, c } void celix_requirement_addDirectives(celix_requirement_t* req, const celix_properties_t* directives) { - const char* visit; - CELIX_PROPERTIES_FOR_EACH(directives, visit) { - const char* val = celix_properties_get(directives, visit, NULL); - celix_requirement_addDirective(req, visit, val); + CELIX_PROPERTIES_ITERATE(directives, visit) { + celix_requirement_addDirective(req, visit.key, visit.entry.value); } } void celix_requirement_addAttributes(celix_requirement_t* req, const celix_properties_t* attributes) { - const char* visit; - CELIX_PROPERTIES_FOR_EACH(attributes, visit) { - const char* val = celix_properties_get(attributes, visit, NULL); - celix_requirement_addAttribute(req, visit, val); + CELIX_PROPERTIES_ITERATE(attributes, visit) { + celix_requirement_addAttribute(req, visit.key, visit.entry.value); } } diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index 7d4f35323..106040866 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -104,8 +104,23 @@ TEST_F(CxxPropertiesTestSuite, WrapTest) { EXPECT_EQ(1, celix_properties_size(props)); { auto cxxProps = celix::Properties::wrap(props); - EXPECT_EQ(1, cxxProps->size()); - EXPECT_EQ(props, cxxProps->getCProperties()); + EXPECT_EQ(1, cxxProps.size()); + EXPECT_EQ(props, cxxProps.getCProperties()); + } //NOTE cxxProps out of scope, but will not destroy celix_properties + EXPECT_EQ(1, celix_properties_size(props)); + + celix_properties_destroy(props); +} + +TEST_F(CxxPropertiesTestSuite, CopyCPropsTest) { + auto *props = celix_properties_create(); + celix_properties_set(props, "test", "test"); + + EXPECT_EQ(1, celix_properties_size(props)); + { + auto cxxProps = celix::Properties::copy(props); + EXPECT_EQ(1, cxxProps.size()); + EXPECT_NE(props, cxxProps.getCProperties()); } //NOTE cxxProps out of scope, but will not destroy celix_properties EXPECT_EQ(1, celix_properties_size(props)); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 40a36f59e..c03dcc383 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -316,9 +316,8 @@ TEST_F(PropertiesTestSuite, SizeAndIteratorTest) { EXPECT_EQ(4, celix_properties_size(props)); int count = 0; - const char *key; - CELIX_PROPERTIES_FOR_EACH(props, key) { - EXPECT_NE(key, nullptr); + CELIX_PROPERTIES_ITERATE(props, entry) { + EXPECT_NE(entry.key, nullptr); count++; } EXPECT_EQ(4, count); @@ -399,26 +398,6 @@ TEST_F(PropertiesTestSuite, GetEntryTest) { celix_properties_destroy(props); } -TEST_F(PropertiesTestSuite, IteratorNextKeyTest) { - auto* props = celix_properties_create(); - celix_properties_set(props, "key1", "value1"); - celix_properties_set(props, "key2", "value2"); - celix_properties_set(props, "key3", "value3"); - auto iter = celix_propertiesIterator_construct(props); - const char* key; - int count = 0; - while (celix_propertiesIterator_hasNext(&iter)) { - key = celix_propertiesIterator_nextKey(&iter); - EXPECT_NE(strstr(key, "key"), nullptr); - count++; - } - EXPECT_EQ(count, 3); - key = celix_propertiesIterator_nextKey(&iter); - EXPECT_EQ(nullptr, key) << "got key: " << key; - - celix_properties_destroy(props); -} - TEST_F(PropertiesTestSuite, IteratorNextTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); @@ -559,6 +538,32 @@ TEST_F(PropertiesTestSuite, SetWithCopyTest) { celix_properties_destroy(props); } +TEST_F(PropertiesTestSuite, SetEntryTest) { + auto* props1 = celix_properties_create(); + auto* props2 = celix_properties_create(); + celix_properties_set(props1, "key1", "value1"); + celix_properties_setLong(props1, "key2", 123); + celix_properties_setBool(props1, "key3", true); + celix_properties_setDouble(props1, "key4", 3.14); + auto* version = celix_version_create(1, 2, 3, nullptr); + celix_properties_setVersion(props1, "key5", version); + celix_version_destroy(version); + + CELIX_PROPERTIES_ITERATE(props1, visit) { + celix_properties_setEntry(props2, visit.key, &visit.entry); + } + + EXPECT_EQ(5, celix_properties_size(props2)); + EXPECT_STREQ("value1", celix_properties_get(props2, "key1", nullptr)); + EXPECT_EQ(123, celix_properties_getAsLong(props2, "key2", -1L)); + EXPECT_EQ(true, celix_properties_getAsBool(props2, "key3", false)); + EXPECT_EQ(3.14, celix_properties_getAsDouble(props2, "key4", -1.0)); + EXPECT_EQ(CELIX_PROPERTIES_VALUE_TYPE_VERSION, celix_properties_getType(props2, "key5")); + + celix_properties_destroy(props1); + celix_properties_destroy(props2); +} + TEST_F(PropertiesTestSuite, DeprecatedApiTest) { //Check if the deprecated api can still be used auto* props = properties_create(); diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index e451ddc1d..6e7a2c312 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -242,9 +242,17 @@ namespace celix { * @brief Wrap C properties and returns it as const in a shared_ptr, * but does not take ownership -> dtor will not destroy C properties. */ - static std::shared_ptr wrap(const celix_properties_t* wrapProps) { + static const Properties wrap(const celix_properties_t* wrapProps) { auto* cp = const_cast(wrapProps); - return std::shared_ptr{new Properties{cp, false}}; + return Properties{cp, false}; + } + + /** + * @brief Wrap C properties and returns it as const in a shared_ptr, + * but does not take ownership -> dtor will not destroy C properties. + */ + static const Properties wrap(celix_properties_t* wrapProps) { + return Properties{wrapProps, false}; } /** @@ -254,6 +262,13 @@ namespace celix { return Properties{wrapProps, true}; } + /** + * @brief Copy C properties and take ownership -> dtor will destroy C properties. + */ + static Properties copy(const celix_properties_t* copyProps) { + return Properties{celix_properties_copy(copyProps), true}; + } + /** * Get the C properties object. * diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index c839473ac..bf9ff2484 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -70,7 +70,7 @@ typedef enum celix_properties_value_type { } celix_properties_value_type_e; /** - * @brief A structure representing a single entry in a property set. + * @brief A structure representing a single value entry in a property set. */ typedef struct celix_properties_entry { const char* value; /**< The string value or string representation of a non-string @@ -221,6 +221,15 @@ CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t* properties, con */ CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); +/** + * @brief Set the value of a property based on the provided property entry, maintaining underlying type. + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling + * this function. + */ +CELIX_UTILS_EXPORT void celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry); + /** * @brief Unset a property, removing it from the property set. * @@ -442,75 +451,6 @@ CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_i for (celix_properties_iterator_t iterName = celix_properties_begin((props)); \ !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) - -/**** Deprecated API *************************************************************************************************/ - -/** - * @brief Constructs a new properties iterator. - * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. - * - * Note: The iterator is initialized to be before the first entry. To advance to the first entry, - * call `celix_propertiesIterator_nextEntry`. - * - * @param[in] properties The properties object to iterate over. - * @return The newly constructed iterator. - */ -CELIX_UTILS_DEPRECATED_EXPORT celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t* properties); - -/** - * @brief Determines whether the iterator has more entries. - * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. - * - * @param[in] iter The iterator. - * @return true if the iterator has more entries, false otherwise. - */ -CELIX_UTILS_DEPRECATED_EXPORT bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); - -/** - * @brief Advances the iterator to the next entry and returns the key for the current entry. - * @deprecated This function is deprecated, use celix_properties_begin, celix_propertiesIterator_next and celix_propertiesIterator_isEnd instead. - * - * If the iterator has no more entries, this function returns NULL. - * - * @param[in, out] iter The iterator. - * @return The key for the current entry, or NULL if the iterator has no more entries. - */ -CELIX_UTILS_DEPRECATED_EXPORT const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t* iter); - -/** - * @brief Macro for iterating over the properties in a property set. - * @deprecated This macro is deprecated, use CELIX_PROPERTIES_ITERATE instead. - * - * @param[in] properties The property set to iterate over. - * @param[out] key The variable to use for the current key in the loop. - * - * - * Example usage: - * @code{.c} - * celix_properties_t* properties = celix_properties_create(); - * celix_properties_set(properties, "key1", "value1"); - * celix_properties_set(properties, "key2", "value2"); - * celix_properties_set(properties, "key3", "value3"); - * - * const char* key; - * CELIX_PROPERTIES_FOR_EACH(properties, key) { - * printf("%s = %s\n", key, celix_properties_get(properties, key, "")); - * } - * @endcode - * - * Output: - * @code{.c} - * key1 = value1 - * key2 = value2 - * key3 = value3 - * @endcode -*/ -#define CELIX_PROPERTIES_FOR_EACH(props, key) _CELIX_PROPERTIES_FOR_EACH(CELIX_UNIQUE_ID(iter), props, key) - -#define _CELIX_PROPERTIES_FOR_EACH(iter, props, key) \ - for(celix_properties_iterator_t iter = celix_propertiesIterator_construct(props); \ - celix_propertiesIterator_hasNext(&iter), (key) = celix_propertiesIterator_nextKey(&iter);) - #ifdef __cplusplus } #endif diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 6b208c3ab..7514c210e 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -317,6 +317,7 @@ static void celix_properties_createAndSetEntry( } celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, boolValue, versionValue); + const char* mapKey = key; if (!celix_stringHashMap_hasKey(properties->map, key)) { //new entry, needs new allocated key; @@ -657,6 +658,28 @@ void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, } } +void celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry) { + if (properties != NULL && key != NULL && entry != NULL) { + switch (entry->valueType) { + case CELIX_PROPERTIES_VALUE_TYPE_LONG: + celix_properties_setLong(properties, key, entry->typed.longValue); + break; + case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: + celix_properties_setDouble(properties, key, entry->typed.doubleValue); + break; + case CELIX_PROPERTIES_VALUE_TYPE_BOOL: + celix_properties_setBool(properties, key, entry->typed.boolValue); + break; + case CELIX_PROPERTIES_VALUE_TYPE_VERSION: + celix_properties_setVersion(properties, key, entry->typed.versionValue); + break; + default: //STRING + celix_properties_set(properties, key, entry->value); + break; + } + } +} + void celix_properties_unset(celix_properties_t *properties, const char *key) { if (properties != NULL) { celix_stringHashMap_remove(properties->map, key); @@ -770,49 +793,6 @@ typedef struct { const celix_properties_t* props; } celix_properties_iterator_internal_t; -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties) { - celix_properties_iterator_internal_t internalIter; - internalIter.mapIter = celix_stringHashMap_begin(properties->map); - internalIter.props = properties; - - celix_properties_iterator_t iter; - iter.index = 0; - iter.key = NULL; - memset(&iter.entry, 0, sizeof(iter.entry)); - - CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(iter._data)); - memset(&iter._data, 0, sizeof(iter._data)); - memcpy(iter._data, &internalIter, sizeof(internalIter)); - return iter; -} - -bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter) { - celix_properties_iterator_internal_t internalIter; - memcpy(&internalIter, iter->_data, sizeof(internalIter)); - return !celix_stringHashMapIterator_isEnd(&internalIter.mapIter); -} - -const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter) { - celix_properties_iterator_internal_t internalIter; - memcpy(&internalIter, iter->_data, sizeof(internalIter)); - - //note assigning key first and then move the next, because celix string hash map iter start at the beginning - const char* key = internalIter.mapIter.key; - iter->index = (int)internalIter.mapIter.index; - celix_properties_entry_t* entry = internalIter.mapIter.value.ptrValue; - if (entry != NULL) { - iter->key = internalIter.mapIter.key; - memcpy(&iter->entry, entry, sizeof(iter->entry)); - } else { - iter->key = NULL; - memset(&iter->entry, 0, sizeof(iter->entry)); - } - celix_stringHashMapIterator_next(&internalIter.mapIter); - - memcpy(iter->_data, &internalIter, sizeof(internalIter)); - return key; -} - celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties) { CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); celix_properties_iterator_internal_t internalIter; From 2765f6a9bf65e1b65d17fae43d3afe2ebc33d3fb Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 15 Oct 2023 13:51:26 +0200 Subject: [PATCH 22/63] #509: Add CMakeUserPresets.json to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index e0327b447..65ddf3698 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ cmake-build* nbproject target html +CMakeUserPresets.json + From 88e513dd404bd66e095382db97fba34181a77b73 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 15 Oct 2023 13:58:38 +0200 Subject: [PATCH 23/63] #509: Replace props FOR_EACH to ITERATE macro in rsa shm & zeroconf disc --- .../src/discovery_zeroconf_announcer.c | 9 +++++---- .../rsa_shm/src/rsa_shm_client.c | 6 ++---- .../rsa_shm/src/rsa_shm_impl.c | 20 +++++++++---------- 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c index 40c38be76..219f558a8 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c @@ -342,8 +342,7 @@ static void discoveryZeroconfAnnouncer_revokeEndpoints(discovery_zeroconf_announ static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroconf_announcer_t *announcer, celix_properties_iterator_t *propIter, TXTRecordRef *txtRecord, uint16_t maxTxtLen, bool splitTxtRecord) { const char *key; const char *val; - while (celix_propertiesIterator_hasNext(propIter)) { - celix_propertiesIterator_next(propIter); + while (!celix_propertiesIterator_isEnd(propIter)) { key = propIter->key; val = propIter->entry.value; if (key) { @@ -356,6 +355,7 @@ static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroc break; } } + celix_propertiesIterator_next(propIter); } return true; } @@ -373,7 +373,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno } char txtBuf[DZC_MAX_TXT_RECORD_SIZE] = {0}; TXTRecordRef txtRecord; - celix_properties_iterator_t propIter = celix_propertiesIterator_construct(entry->properties); + celix_properties_iterator_t propIter = celix_properties_begin(entry->properties); TXTRecordCreate(&txtRecord, sizeof(txtBuf), txtBuf); char propSizeStr[16]= {0}; @@ -410,7 +410,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno if (registered) { entry->registerRef = dsRef; - while (celix_propertiesIterator_hasNext(&propIter)) { + while (!celix_propertiesIterator_isEnd(&propIter)) { TXTRecordCreate(&txtRecord, sizeof(txtBuf), txtBuf); if (!discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(announcer, &propIter, &txtRecord, sizeof(txtBuf), true)) { TXTRecordDeallocate(&txtRecord); @@ -424,6 +424,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno break; } TXTRecordDeallocate(&txtRecord); + celix_propertiesIterator_next(&propIter); } } } diff --git a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_client.c b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_client.c index 12a9e3ed2..7c236f7a8 100644 --- a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_client.c +++ b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_client.c @@ -257,7 +257,6 @@ celix_status_t rsaShmClientManager_sendMsgTo(rsa_shm_client_manager_t *clientMan || response == NULL) { return CELIX_ILLEGAL_ARGUMENT; } - const char *key = NULL; size_t metadataStringSize = 0; FILE *fp = NULL; rsa_shm_msg_control_t *msgCtrl = NULL; @@ -279,9 +278,8 @@ celix_status_t rsaShmClientManager_sendMsgTo(rsa_shm_client_manager_t *clientMan return CELIX_ERROR_MAKE(CELIX_FACILITY_CERRNO, errno); } if (metadata != NULL) { - CELIX_PROPERTIES_FOR_EACH(metadata, key) { - const char * value = celix_properties_get(metadata, key,""); - fprintf(fp,"%s=%s\n", key, value); + CELIX_PROPERTIES_ITERATE(metadata, iter) { + fprintf(fp,"%s=%s\n", iter.key, iter.entry.value); } } fclose(fp); diff --git a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c index f31f2777b..03d85b6d5 100755 --- a/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c +++ b/bundles/remote_services/remote_service_admin_shm_v2/rsa_shm/src/rsa_shm_impl.c @@ -231,25 +231,23 @@ static void rsaShm_overlayProperties(celix_properties_t *additionalProperties, c /*The property keys of a service are case-insensitive,while the property keys of the specified additional properties map are case sensitive. * A property key in the additional properties map must therefore override any case variant property key in the properties of the specified Service Reference.*/ - const char *additionalPropKey = NULL; - const char *servicePropKey = NULL; - CELIX_PROPERTIES_FOR_EACH(additionalProperties, additionalPropKey) { - if (strcmp(additionalPropKey,(char*) OSGI_FRAMEWORK_OBJECTCLASS) != 0 - && strcmp(additionalPropKey,(char*) OSGI_FRAMEWORK_SERVICE_ID) != 0) { + CELIX_PROPERTIES_ITERATE(additionalProperties, additionalPropIter) { + if (strcmp(additionalPropIter.key,(char*) OSGI_FRAMEWORK_OBJECTCLASS) != 0 + && strcmp(additionalPropIter.key,(char*) OSGI_FRAMEWORK_SERVICE_ID) != 0) { bool propKeyCaseEqual = false; - CELIX_PROPERTIES_FOR_EACH(serviceProperties, servicePropKey) { - if (strcasecmp(additionalPropKey,servicePropKey) == 0) { - const char* val = celix_properties_get(additionalProperties,additionalPropKey,NULL); - celix_properties_set(serviceProperties,servicePropKey,val); + CELIX_PROPERTIES_ITERATE(serviceProperties, servicePropIter) { + if (strcasecmp(additionalPropIter.key,servicePropIter.key) == 0) { + const char* val = additionalPropIter.entry.value; + celix_properties_set(serviceProperties,servicePropIter.key,val); propKeyCaseEqual = true; break; } } if (!propKeyCaseEqual) { - const char* val = celix_properties_get(additionalProperties,additionalPropKey,NULL); - celix_properties_set(serviceProperties,additionalPropKey,val); + const char* val = additionalPropIter.entry.value; + celix_properties_set(serviceProperties,additionalPropIter.key,val); } } } From bd7aa8b2f3277f2a880293382616c65c1fb4754b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 15 Oct 2023 15:24:09 +0200 Subject: [PATCH 24/63] Remove C++17 constructions from updated Properties.h --- libs/utils/gtest/CMakeLists.txt | 3 -- .../utils/gtest/src/CxxPropertiesTestSuite.cc | 11 +++++-- libs/utils/include/celix/Properties.h | 31 +++++++------------ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 600c2df5c..32fbc2c76 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -20,9 +20,6 @@ add_executable(test_utils src/CxxPropertiesTestSuite.cc src/CxxFilterTestSuite.cc src/CxxVersionTestSuite.cc -) - -add_executable(test_utils src/LogTestSuite.cc src/LogUtilsTestSuite.cc src/VersionRangeTestSuite.cc diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index cadec5a2b..214d77010 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -130,6 +130,9 @@ TEST_F(CxxPropertiesTestSuite, CopyCPropsTest) { TEST_F(CxxPropertiesTestSuite, GetTypeTest) { celix::Properties props{}; + const auto v2 = celix::Version{1, 2, 3}; + auto v3 = celix::Version{1, 2, 4}; + props.set("bool", true); props.set("long1", 1l); props.set("long2", (int)1); //should lead to long; @@ -140,7 +143,9 @@ TEST_F(CxxPropertiesTestSuite, GetTypeTest) { props.set("long7", (unsigned char)1); //should lead to long; props.set("double1", 1.0); props.set("double2", 1.0f); //set float should lead to double - props.set("version", celix::Version{1, 2, 3}); + props.set("version1", celix::Version{1, 2, 3}); + props.set("version2", v2); + props.set("version3", v3); EXPECT_EQ(props.getType("bool"), celix::Properties::ValueType::Bool); EXPECT_EQ(props.getType("long1"), celix::Properties::ValueType::Long); @@ -152,7 +157,9 @@ TEST_F(CxxPropertiesTestSuite, GetTypeTest) { EXPECT_EQ(props.getType("long7"), celix::Properties::ValueType::Long); EXPECT_EQ(props.getType("double1"), celix::Properties::ValueType::Double); EXPECT_EQ(props.getType("double2"), celix::Properties::ValueType::Double); - EXPECT_EQ(props.getType("version"), celix::Properties::ValueType::Version); + EXPECT_EQ(props.getType("version1"), celix::Properties::ValueType::Version); + EXPECT_EQ(props.getType("version2"), celix::Properties::ValueType::Version); + EXPECT_EQ(props.getType("version3"), celix::Properties::ValueType::Version); } TEST_F(CxxPropertiesTestSuite, GetAsVersionTest) { diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 814e98c9c..12d488ca3 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -23,12 +23,7 @@ #include #include #include -#if __cplusplus >= 201703L //C++17 or higher -#include -#else #include -#endif - #include "celix_properties.h" #include "celix_utils.h" @@ -101,6 +96,12 @@ namespace celix { private: template using IsString = std::is_same, std::string>; //Util to check if T is a std::string. + + template + using IsVersion = std::is_same, ::celix::Version>; //Util to check if T is a celix::Version. + + template + using IsNotStringOrVersion = std::integral_constant::value && !IsVersion::value>; //Util to check if T is not a std::string or celix::Version. public: using const_iterator = ConstPropertiesIterator; //note currently only a const iterator is supported. @@ -370,7 +371,7 @@ namespace celix { * @param[in] value The value to set for the property. */ template - typename std::enable_if::value>::type + typename std::enable_if<::celix::Properties::IsNotStringOrVersion::value>::type set(const std::string& key, T&& value) { using namespace std; celix_properties_set(cProps.get(), key.c_str(), to_string(value).c_str()); @@ -382,7 +383,9 @@ namespace celix { * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. */ - void set(const std::string& key, celix::Version&& value) { + template + typename std::enable_if<::celix::Properties::IsVersion::value>::type + set(const std::string& key, T&& value) { celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); } @@ -496,8 +499,6 @@ namespace celix { } #endif - - /** * @brief Store the property set to the given file path. * @@ -509,15 +510,9 @@ namespace celix { * @param[in] header An optional header string to include as a comment at the beginning of the file. * @throws celix::IOException If an error occurs while writing to the file. */ -#if __cplusplus >= 201703L //C++17 or higher - void store(std::string_view path, std::string_view header = {}) const { - storeTo(path.data(), header.empty() ? nullptr : header.data()); - } -#else void store(const std::string& path, const std::string& header = {}) const { storeTo(path.data(), header.empty() ? nullptr : header.data()); } -#endif /** * @brief Loads properties from the file at the given path. @@ -525,11 +520,7 @@ namespace celix { * @return A new Properties object containing the properties from the file. * @throws celix::IOException If the file cannot be opened or read. */ -#if __cplusplus >= 201703L //C++17 or higher - static celix::Properties load(std::string_view path) { return loadFrom(path.data()); } -#else static celix::Properties load(const std::string& path) { return loadFrom(path.data()); } -#endif private: Properties(celix_properties_t* props, bool takeOwnership) : @@ -578,4 +569,4 @@ inline std::ostream& operator<<(std::ostream& os, const ::celix::Properties::Val { os << std::string{ref.getValue()}; return os; -} \ No newline at end of file +} From 6d25c2bdbc8553742a689104231714b320b21924 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 15 Oct 2023 19:41:23 +0200 Subject: [PATCH 25/63] Remove use realloc or calloc define in celix_hash_map.c --- libs/utils/src/celix_hash_map.c | 42 --------------------------------- 1 file changed, 42 deletions(-) diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 995b0e6bb..e6113754c 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -27,19 +27,6 @@ #include #include -/** - * Whether to use realloc - instead of calloc - to resize a hash map. - * - * With realloc the memory is increased and the - * map entries are corrected. - * - * With alloc a new buckets array is allocated and - * entries are moved into the new bucket array. - * And the old buckets array is freed. - * - */ -#define CELIX_HASH_MAP_RESIZE_WITH_REALLOC - static unsigned int DEFAULT_INITIAL_CAPACITY = 16; static double DEFAULT_LOAD_FACTOR = 0.75; static unsigned int MAXIMUM_CAPACITY = INT32_MAX/10; @@ -164,7 +151,6 @@ bool celix_hashMap_hasKey(const celix_hash_map_t* map, const char* strKey, long return celix_hashMap_getEntry(map, strKey, longKey) != NULL; } -#ifdef CELIX_HASH_MAP_RESIZE_WITH_REALLOC static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { if (map->bucketsSize == MAXIMUM_CAPACITY) { return; @@ -196,34 +182,6 @@ static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { //update bucketSize to new capacity map->bucketsSize = newCapacity; } -#else -static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { - celix_hash_map_entry_t** newTable; - unsigned int j; - if (map->bucketsSize == MAXIMUM_CAPACITY) { - return; - } - - newTable = calloc(newCapacity, sizeof(celix_hash_map_entry_t*)); - - for (j = 0; j < map->bucketsSize; j++) { - celix_hash_map_entry_t* entry = map->buckets[j]; - if (entry != NULL) { - map->buckets[j] = NULL; - do { - celix_hash_map_entry_t* next = entry->next; - unsigned int i = celix_hashMap_indexFor(entry->hash, newCapacity); - entry->next = newTable[i]; - newTable[i] = entry; - entry = next; - } while (entry != NULL); - } - } - free(map->buckets); - map->buckets = newTable; - map->bucketsSize = newCapacity; -} -#endif static void celix_hashMap_callRemovedCallback(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { if (map->simpleRemovedCallback) { From 747cd8aa73e515b36d70531ef32ee04951bc1d2e Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 15 Oct 2023 19:42:44 +0200 Subject: [PATCH 26/63] Add celix_status_t return to celix properties set functions --- libs/utils/gtest/src/ErrTestSuite.cc | 3 +- .../src/PropertiesErrorInjectionTestSuite.cc | 54 ++++++ libs/utils/gtest/src/PropertiesTestSuite.cc | 35 +++- libs/utils/include/celix/Properties.h | 175 ++++++++++-------- libs/utils/include/celix_properties.h | 21 ++- libs/utils/src/properties.c | 125 ++++++++----- 6 files changed, 275 insertions(+), 138 deletions(-) diff --git a/libs/utils/gtest/src/ErrTestSuite.cc b/libs/utils/gtest/src/ErrTestSuite.cc index 9d27a244a..d1c2b464d 100644 --- a/libs/utils/gtest/src/ErrTestSuite.cc +++ b/libs/utils/gtest/src/ErrTestSuite.cc @@ -23,8 +23,7 @@ class ErrTestSuite : public ::testing::Test { public: - ErrTestSuite() = default; - ~ErrTestSuite() noexcept override { + ErrTestSuite() { celix_err_resetErrors(); } }; diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 270e4ec38..5130e8e8a 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -20,10 +20,13 @@ #include +#include "celix_cleanup.h" #include "celix_properties.h" +#include "celix/Properties.h" #include "malloc_ei.h" #include "celix_hash_map_ei.h" +#include "celix_utils_ei.h" class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: @@ -31,17 +34,68 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { ~PropertiesErrorInjectionTestSuite() override { celix_ei_expect_malloc(nullptr, 0, nullptr); celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } }; TEST_F(PropertiesErrorInjectionTestSuite, CreateFailureTest) { + //C API celix_ei_expect_malloc((void *)celix_properties_create, 0, nullptr); ASSERT_EQ(nullptr, celix_properties_create()); + + //C++ API + celix_ei_expect_malloc((void *)celix_properties_create, 0, nullptr); + ASSERT_THROW(celix::Properties(), std::bad_alloc); } TEST_F(PropertiesErrorInjectionTestSuite, CopyFailureTest) { + //C API celix_autoptr(celix_properties_t) prop = celix_properties_create(); ASSERT_NE(nullptr, prop); celix_ei_expect_celix_stringHashMap_createWithOptions((void *)celix_properties_create, 0, nullptr); ASSERT_EQ(nullptr, celix_properties_copy(prop)); + + //C++ API + const celix::Properties cxxProp{}; + celix_ei_expect_celix_stringHashMap_createWithOptions((void *)celix_properties_create, 0, nullptr); + ASSERT_THROW(celix::Properties{cxxProp}, std::bad_alloc); +} + +//TODO store, load, setWithoutCopy + +TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { + //C API + //Given a celix properties object with a filled optimization cache + celix_autoptr(celix_properties_t) props = celix_properties_create(); + for (int i = 0; i < 50; ++i) { + const char* val = "1234567890"; + char key[10]; + snprintf(key, sizeof(key), "key%i", i); + celix_properties_set(props, key, val); + } + + //When a malloc error injection is set for celix_properties_set with level 2 (during alloc entry) + celix_ei_expect_malloc((void *)celix_properties_set, 3, nullptr); + //Then the celix_properties_set call fails + ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); + + //When a celix_utils_strdup error injection is set for celix_properties_set with level 4 (during strdup key) + celix_ei_expect_celix_utils_strdup((void *)celix_properties_set, 4, nullptr); + //Then the celix_properties_set call fails + ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); + + //C++ API + //Given a celix properties object with a filled optimization cache + celix::Properties cxxProps{}; + for (int i = 0; i < 50; ++i) { + const char* val = "1234567890"; + char key[10]; + snprintf(key, sizeof(key), "key%i", i); + cxxProps.set(key, val); + } + + //When a malloc error injection is set for celix_properties_set with level 2 and ordinal 1 (during alloc entry) + celix_ei_expect_malloc((void *)celix_properties_set, 3, nullptr, 1); + //Then the Properties:set throws a bad_alloc exception + ASSERT_THROW(cxxProps.set("key", "value"), std::bad_alloc); } diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index c03dcc383..91844c17d 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -69,16 +69,35 @@ TEST_F(PropertiesTestSuite, LoadFromStringTest) { TEST_F(PropertiesTestSuite, StoreTest) { const char* propertiesFile = "resources-test/properties_out.txt"; - auto* properties = celix_properties_create(); - char keyA[] = "x"; - char keyB[] = "y"; - char valueA[] = "1"; - char valueB[] = "2"; - celix_properties_set(properties, keyA, valueA); - celix_properties_set(properties, keyB, valueB); + celix_autoptr(celix_properties_t) properties = celix_properties_create(); + celix_properties_set(properties, "keyA", "valueA"); + celix_properties_set(properties, "keyB", "valueB"); celix_properties_store(properties, propertiesFile, nullptr); - celix_properties_destroy(properties); + celix_autoptr(celix_properties_t) properties2 = celix_properties_load(propertiesFile); + EXPECT_EQ(celix_properties_size(properties), celix_properties_size(properties2)); + EXPECT_STREQ(celix_properties_get(properties, "keyA", ""), celix_properties_get(properties2, "keyA", "")); + EXPECT_STREQ(celix_properties_get(properties, "keyB", ""), celix_properties_get(properties2, "keyB", "")); +} + +TEST_F(PropertiesTestSuite, StoreWithHeaderTest) { + const char* propertiesFile = "resources-test/properties_with_header_out.txt"; + celix_autoptr(celix_properties_t) properties = celix_properties_create(); + celix_properties_set(properties, "keyA", "valueA"); + celix_properties_set(properties, "keyB", "valueB"); + celix_properties_store(properties, propertiesFile, "header"); + + celix_autoptr(celix_properties_t) properties2 = celix_properties_load(propertiesFile); + EXPECT_EQ(celix_properties_size(properties), celix_properties_size(properties2)); + EXPECT_STREQ(celix_properties_get(properties, "keyA", ""), celix_properties_get(properties2, "keyA", "")); + EXPECT_STREQ(celix_properties_get(properties, "keyB", ""), celix_properties_get(properties2, "keyB", "")); + + //check if provided header text is present in file + FILE *f = fopen(propertiesFile, "r"); + char line[1024]; + fgets(line, sizeof(line), f); + EXPECT_STREQ("#header\n", line); + fclose(f); } TEST_F(PropertiesTestSuite, GetAsLongTest) { diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 12d488ca3..7262733d1 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -19,6 +19,7 @@ #pragma once +#include #include #include #include @@ -52,7 +53,7 @@ namespace celix { return *this; } - const ConstPropertiesIterator& operator*() { + const ConstPropertiesIterator& operator*() const { return *this; } @@ -101,7 +102,22 @@ namespace celix { using IsVersion = std::is_same, ::celix::Version>; //Util to check if T is a celix::Version. template - using IsNotStringOrVersion = std::integral_constant::value && !IsVersion::value>; //Util to check if T is not a std::string or celix::Version. + using IsIntegral = std::integral_constant>::value + && !std::is_same, bool>::value>; //Util to check if T is an integral type. + + template + using IsFloatingPoint = std::is_floating_point>; //Util to check if T is a floating point type. + + template + using IsBoolean = std::is_same, bool>; //Util to check if T is a boolean type. + + template + using IsCharPointer = std::is_same, const char*>; //Util to check if T is a const char* type. + + template + using IsNotStringVersionIntegralFloatingPointOrBoolean = + std::integral_constant::value && !IsVersion::value && !IsCharPointer::value && + !IsIntegral::value && !IsFloatingPoint::value && !IsBoolean::value>; public: using const_iterator = ConstPropertiesIterator; //note currently only a const iterator is supported. @@ -137,7 +153,7 @@ namespace celix { return *this; } - [[nodiscard]] const char* getValue() const { + const char* getValue() const { if (charKey == nullptr) { return celix_properties_get(props.get(), stringKey.c_str(), nullptr); } else { @@ -156,20 +172,19 @@ namespace celix { }; - Properties() : cProps{celix_properties_create(), [](celix_properties_t* p) { celix_properties_destroy(p); }} {} + Properties() : cProps{createCProps(celix_properties_create())} {} Properties(Properties&&) = default; Properties& operator=(Properties&&) = default; Properties& operator=(const Properties &rhs) { if (this != &rhs) { - cProps = std::shared_ptr{celix_properties_copy(rhs.cProps.get()), [](celix_properties_t* p) { celix_properties_destroy(p); }}; + cProps = createCProps(celix_properties_copy(rhs.cProps.get())); } return *this; } - Properties(const Properties& rhs) : - cProps{celix_properties_copy(rhs.cProps.get()), [](celix_properties_t* p) { celix_properties_destroy(p); }} {} + Properties(const Properties& rhs) : cProps{createCProps(celix_properties_copy(rhs.cProps.get()))} {} Properties(std::initializer_list> list) : cProps{celix_properties_create(), [](celix_properties_t* p) { celix_properties_destroy(p); }} { for(auto &entry : list) { @@ -214,7 +229,7 @@ namespace celix { * @warning Try not the depend on the C API from a C++ bundle. If features are missing these should be added to * the C++ API. */ - [[nodiscard]] celix_properties_t* getCProperties() const { + celix_properties_t* getCProperties() const { return cProps.get(); } @@ -235,35 +250,35 @@ namespace celix { /** * @brief begin iterator */ - [[nodiscard]] const_iterator begin() const noexcept { + const_iterator begin() const noexcept { return ConstPropertiesIterator{cProps.get()}; } /** * @brief end iterator */ - [[nodiscard]] const_iterator end() const noexcept { + const_iterator end() const noexcept { return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } /** * @brief constant begin iterator */ - [[nodiscard]] const_iterator cbegin() const noexcept { + const_iterator cbegin() const noexcept { return ConstPropertiesIterator{cProps.get()}; } /** * @brief constant end iterator */ - [[nodiscard]] const_iterator cend() const noexcept { + const_iterator cend() const noexcept { return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } /** * @brief Get the value for a property key or return the defaultValue if the key does not exists. */ - [[nodiscard]] std::string get(const std::string& key, const std::string& defaultValue = {}) const { + std::string get(const std::string& key, const std::string& defaultValue = {}) const { const char* found = celix_properties_get(cProps.get(), key.c_str(), nullptr); return found == nullptr ? std::string{defaultValue} : std::string{found}; } @@ -276,7 +291,7 @@ namespace celix { * to a long. * @return The long value of the property if it exists and can be converted, or the default value otherwise. */ - [[nodiscard]] long getAsLong(const std::string& key, long defaultValue) const { + long getAsLong(const std::string& key, long defaultValue) const { return celix_properties_getAsLong(cProps.get(), key.c_str(), defaultValue); } @@ -288,7 +303,7 @@ namespace celix { * to a double. * @return The double value of the property if it exists and can be converted, or the default value otherwise. */ - [[nodiscard]] double getAsDouble(const std::string &key, double defaultValue) const { + double getAsDouble(const std::string &key, double defaultValue) const { return celix_properties_getAsDouble(cProps.get(), key.c_str(), defaultValue); } @@ -300,7 +315,7 @@ namespace celix { * to a boolean. * @return The boolean value of the property if it exists and can be converted, or the default value otherwise. */ - [[nodiscard]] bool getAsBool(const std::string &key, bool defaultValue) const { + bool getAsBool(const std::string &key, bool defaultValue) const { return celix_properties_getAsBool(cProps.get(), key.c_str(), defaultValue); } @@ -315,7 +330,7 @@ namespace celix { * @return The value of the property if it is a Celix version, or the default value if the property is not set * or the value is not a Celix version. */ - [[nodiscard]] celix::Version getAsVersion(const std::string& key, celix::Version defaultValue = {}) { + celix::Version getAsVersion(const std::string& key, celix::Version defaultValue = {}) { auto* cVersion = celix_properties_getAsVersion(cProps.get(), key.data(), nullptr); if (cVersion) { celix::Version version{ @@ -329,39 +344,36 @@ namespace celix { return defaultValue; } - /** - * @brief Get the type of the property with key. - * - * @param[in] key The key of the property to get the type for. - * @return The type of the property with the given key, or ValueType::Unset if the property - * does not exist. - */ - [[nodiscard]] ValueType getType(const std::string& key) { - return getAndConvertType(cProps, key.data()); - } - /** * @brief Set the value of a property. * * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ - void set(const std::string& key, const std::string& value) { - celix_properties_set(cProps.get(), key.data(), value.c_str()); + template + typename std::enable_if::value>::type + set(const std::string& key, T&& value) { + auto status = celix_properties_set(cProps.get(), key.data(), value.c_str()); + throwIfEnomem(status); } /** - * @brief Set the value of a property. + * @brief Set string property value for a given key. * * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ - void set(const std::string& key, const char* value) { - celix_properties_set(cProps.get(), key.data(), value); + template + typename std::enable_if::value>::type + set(const std::string& key, T&& value) { + auto status = celix_properties_set(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** - * @brief Sets a property with a value of type T. + * @brief Set a property with a to_string value of type T. * * This function will use the std::to_string function to convert the value of type T to a string, * which will be used as the value for the property. @@ -369,12 +381,14 @@ namespace celix { * @tparam T The type of the value to set. * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ template - typename std::enable_if<::celix::Properties::IsNotStringOrVersion::value>::type + typename std::enable_if<::celix::Properties::IsNotStringVersionIntegralFloatingPointOrBoolean::value>::type set(const std::string& key, T&& value) { using namespace std; - celix_properties_set(cProps.get(), key.c_str(), to_string(value).c_str()); + auto status = celix_properties_set(cProps.get(), key.c_str(), to_string(value).c_str()); + throwIfEnomem(status); } /** @@ -382,11 +396,13 @@ namespace celix { * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ template typename std::enable_if<::celix::Properties::IsVersion::value>::type set(const std::string& key, T&& value) { - celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); + auto status = celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); + throwIfEnomem(status); } /** @@ -394,29 +410,13 @@ namespace celix { * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ - void set(const std::string& key, bool value) { - celix_properties_setBool(cProps.get(), key.data(), value); - } - - /** - * @brief Sets a long property value for a given key. - * - * @param[in] key The key of the property to set. - * @param[in] value The value to set for the property. - */ - void set(const std::string& key, long value) { - celix_properties_setLong(cProps.get(), key.data(), value); - } - - /** - * @brief Sets a long property value for a given key. - * - * @param[in] key The key of the property to set. - * @param[in] value The value to set for the property. - */ - void set(const std::string& key, int value) { - celix_properties_setLong(cProps.get(), key.data(), value); + template + typename std::enable_if<::celix::Properties::IsBoolean::value>::type + set(const std::string& key, T&& value) { + auto status = celix_properties_setBool(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** @@ -424,9 +424,13 @@ namespace celix { * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ - void set(const std::string& key, unsigned int value) { - celix_properties_setLong(cProps.get(), key.data(), value); + template + typename std::enable_if<::celix::Properties::IsIntegral::value>::type + set(const std::string& key, T&& value) { + auto status = celix_properties_setLong(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** @@ -434,9 +438,13 @@ namespace celix { * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ - void set(const std::string& key, double value) { - celix_properties_setDouble(cProps.get(), key.data(), value); + template + typename std::enable_if<::celix::Properties::IsFloatingPoint::value>::type + set(const std::string& key, T&& value) { + auto status = celix_properties_setDouble(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** @@ -444,31 +452,35 @@ namespace celix { * * @param[in] key The key of the property to set. * @param[in] value The value to set for the property. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ void set(const std::string& key, const celix_version_t* value) { - celix_properties_setVersion(cProps.get(), key.data(), value); + auto status = celix_properties_setVersion(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** - * @brief Sets a String property. + * @brief Returns the number of properties in the Properties object. */ - template - typename std::enable_if<::celix::Properties::IsString::value>::type - set(const std::string& key, T&& value) { - celix_properties_set(cProps.get(), key.c_str(), value.c_str()); + std::size_t size() const { + return celix_properties_size(cProps.get()); } /** - * @brief Returns the number of properties in the Properties object. + * @brief Get the type of the property with key. + * + * @param[in] key The key of the property to get the type for. + * @return The type of the property with the given key, or ValueType::Unset if the property + * does not exist. */ - [[nodiscard]] std::size_t size() const { - return celix_properties_size(cProps.get()); + ValueType getType(const std::string& key) { + return getAndConvertType(cProps, key.data()); } /** * @brief Convert the properties a (new) std::string, std::string map. */ - [[nodiscard]] std::map convertToMap() const { + std::map convertToMap() const { std::map result{}; for (const auto& pair : *this) { result[std::string{pair.first}] = pair.second; @@ -479,7 +491,7 @@ namespace celix { /** * @brief Convert the properties a (new) std::string, std::string unordered map. */ - [[nodiscard]] std::unordered_map convertToUnorderedMap() const { + std::unordered_map convertToUnorderedMap() const { std::unordered_map result{}; for (const auto& pair : *this) { result[std::string{pair.first}] = pair.second; @@ -507,7 +519,7 @@ namespace celix { * If a non-empty header string is provided, it will be written as a comment at the beginning of the file. * * @param[in] file The file to store the properties to. - * @param[in] header An optional header string to include as a comment at the beginning of the file. + * @param[in] header An optional (single line) header string to include as a comment at the beginning of the file. * @throws celix::IOException If an error occurs while writing to the file. */ void store(const std::string& path, const std::string& header = {}) const { @@ -526,6 +538,19 @@ namespace celix { Properties(celix_properties_t* props, bool takeOwnership) : cProps{props, [takeOwnership](celix_properties_t* p){ if (takeOwnership) { celix_properties_destroy(p); }}} {} + std::shared_ptr createCProps(celix_properties_t* p) { + if (!p) { + throw std::bad_alloc(); + } + return std::shared_ptr{p, [](celix_properties_t* p) { celix_properties_destroy(p); }}; + } + + void throwIfEnomem(int status) { + if (status == CELIX_ENOMEM) { + throw std::bad_alloc(); + } + } + static celix::Properties::ValueType getAndConvertType( const std::shared_ptr& cProperties, const char* key) { diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index bf9ff2484..f751ca743 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -165,7 +165,8 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const cha * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. - * @param[in] header An optional header to write to the file before the properties. + * @param[in] header An optional - single line - header to write to the file before the properties. + * Will be prefix with a '#' character. * @return CELIX_SUCCESS if the operation was successful, CELIX_FILE_IO_EXCEPTION if there was an error writing to the * file. */ @@ -205,8 +206,9 @@ CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t* properties, const char* key, const char *value); +CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char *value); /** * @brief Set the value of a property without copying the key and value strings. @@ -227,8 +229,9 @@ CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t* prop * @param[in] key The key of the property to set. * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling * this function. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry); /** * @brief Unset a property, removing it from the property set. @@ -265,8 +268,9 @@ CELIX_UTILS_EXPORT long celix_properties_getAsLong(const celix_properties_t* pro * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The long value to set the property to. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_setLong(celix_properties_t* properties, const char* key, long value); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setLong(celix_properties_t* properties, const char* key, long value); /** * @brief Get the value of a property as a boolean. @@ -287,8 +291,9 @@ CELIX_UTILS_EXPORT bool celix_properties_getAsBool(const celix_properties_t* pro * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The boolean value to set the property to. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); /** * @brief Set the value of a property to a double. @@ -296,8 +301,9 @@ CELIX_UTILS_EXPORT void celix_properties_setBool(celix_properties_t* properties, * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The double value to set the property to. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); /** * @brief Get the value of a property as a double. @@ -320,8 +326,9 @@ CELIX_UTILS_EXPORT double celix_properties_getAsDouble(const celix_properties_t* * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] version The value to set. The function will make a copy of this object and store it in the property set. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT void celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); /** * @brief Set the value of a property as a Celix version. diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 7514c210e..04bb63f2c 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -28,6 +28,7 @@ #include #include +#include "celix_err.h" #include "celix_build_assert.h" #include "celix_utils.h" #include "celix_string_hash_map.h" @@ -257,7 +258,7 @@ static celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* * provided key and value strings. */ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_properties_t *properties, - char *strValue) { + const char *strValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { return NULL; @@ -275,7 +276,6 @@ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_pr */ static celix_properties_entry_t* celix_properties_createEntry( celix_properties_t *properties, - const char *key, const char** strValue, const long* longValue, const double* doubleValue, @@ -300,32 +300,66 @@ static celix_properties_entry_t* celix_properties_createEntry( return entry; } +static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) { + celix_properties_freeString(properties, (char*)entry->value); + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy(entry->typed.versionValue); + } + if (entry >= properties->entriesBuffer && + entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { + //entry is part of the properties entries buffer -> nop. + } else { + free(entry); + } +} + /** * Create and add entry and optionally use the short properties optimization buffers. * Only 1 of the types values (strValue, LongValue, etc) should be provided. */ -static void celix_properties_createAndSetEntry( +static celix_status_t celix_properties_createAndSetEntry( celix_properties_t *properties, - const char *key, + const char* key, const char** strValue, const long* longValue, const double* doubleValue, const bool* boolValue, celix_version_t* versionValue) { - if (properties == NULL) { - return; + if (!properties) { + return CELIX_SUCCESS; //silently ignore + } + if (!key) { + celix_err_push("Cannot set property with NULL key"); + return CELIX_SUCCESS; //print error and ignore } - celix_properties_entry_t* entry = celix_properties_createEntry(properties, key, strValue, longValue, doubleValue, + + celix_properties_entry_t* entry = celix_properties_createEntry(properties, strValue, longValue, doubleValue, boolValue, versionValue); + if (!entry) { + return CELIX_ENOMEM; + } const char* mapKey = key; if (!celix_stringHashMap_hasKey(properties->map, key)) { //new entry, needs new allocated key; mapKey = celix_properties_createString(properties, key); + if (!mapKey) { + celix_properties_destroyEntry(properties, entry); + return CELIX_ENOMEM; + } } - if (entry != NULL) { - celix_stringHashMap_put(properties->map, mapKey, entry); - } + + //TODO +// celix_status_t status = celix_stringHashMap_put(properties->map, mapKey, entry); +// if (status != CELIX_SUCCESS) { +// celix_properties_destroyEntry(properties, entry); +// if (mapKey != key) { +// celix_properties_freeString(properties, (char*)mapKey); +// } +// } + + celix_stringHashMap_put(properties->map, mapKey, entry); + return CELIX_SUCCESS; } static void celix_properties_removeKeyCallback(void* handle, char* key) { @@ -336,16 +370,7 @@ static void celix_properties_removeKeyCallback(void* handle, char* key) { static void celix_properties_removeEntryCallback(void* handle, const char* key __attribute__((unused)), celix_hash_map_value_t val) { celix_properties_t* properties = handle; celix_properties_entry_t* entry = val.ptrValue; - celix_properties_freeString(properties, (char*)entry->value); - if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - celix_version_destroy(entry->typed.versionValue); - } - if (entry >= properties->entriesBuffer && - entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { - //entry is part of the properties entries buffer -> nop. - } else { - free(entry); - } + celix_properties_destroyEntry(properties, entry); } celix_properties_t* celix_properties_create(void) { @@ -386,10 +411,11 @@ celix_properties_t* celix_properties_load(const char *filename) { } static void celix_properties_parseLine(const char* line, celix_properties_t *props) { - int linePos = 0; + int linePos; + int outputPos; + bool precedingCharIsBackslash = false; bool isComment = false; - int outputPos = 0; char *output = NULL; int key_len = MALLOC_BLOCK_SIZE; int value_len = MALLOC_BLOCK_SIZE; @@ -575,6 +601,19 @@ celix_status_t celix_properties_store(celix_properties_t *properties, const char } int rc = 0; + + if (header && strstr("\n", header)) { + celix_err_push("Header cannot contain newlines. Ignoring header."); + } else if (header) { + rc = fputc('#', file); + if (rc != 0) { + rc = fputs(header, file); + } + if (rc != 0) { + rc = fputs("\n", file); + } + } + CELIX_PROPERTIES_ITERATE(properties, iter) { const char* val = iter.entry.value; if (rc != EOF) { @@ -640,10 +679,8 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr return entry; } -void celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { - if (properties != NULL && key != NULL) { - celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL); - } +celix_status_t celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { + return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL); } void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { @@ -658,26 +695,22 @@ void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, } } -void celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry) { - if (properties != NULL && key != NULL && entry != NULL) { +celix_status_t celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry) { + if (entry) { switch (entry->valueType) { case CELIX_PROPERTIES_VALUE_TYPE_LONG: - celix_properties_setLong(properties, key, entry->typed.longValue); - break; + return celix_properties_setLong(properties, key, entry->typed.longValue); case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: - celix_properties_setDouble(properties, key, entry->typed.doubleValue); - break; + return celix_properties_setDouble(properties, key, entry->typed.doubleValue); case CELIX_PROPERTIES_VALUE_TYPE_BOOL: - celix_properties_setBool(properties, key, entry->typed.boolValue); - break; + return celix_properties_setBool(properties, key, entry->typed.boolValue); case CELIX_PROPERTIES_VALUE_TYPE_VERSION: - celix_properties_setVersion(properties, key, entry->typed.versionValue); - break; + return celix_properties_setVersion(properties, key, entry->typed.versionValue); default: //STRING - celix_properties_set(properties, key, entry->value); - break; + return celix_properties_set(properties, key, entry->value); } } + return CELIX_SUCCESS; //silently ignore NULL entry } void celix_properties_unset(celix_properties_t *properties, const char *key) { @@ -702,8 +735,8 @@ long celix_properties_getAsLong(const celix_properties_t *props, const char *key return result; } -void celix_properties_setLong(celix_properties_t *props, const char *key, long value) { - celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL); +celix_status_t celix_properties_setLong(celix_properties_t *props, const char *key, long value) { + return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL); } double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { @@ -722,8 +755,8 @@ double celix_properties_getAsDouble(const celix_properties_t *props, const char return result; } -void celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { - celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL); +celix_status_t celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { + return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL); } bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { @@ -744,8 +777,8 @@ bool celix_properties_getAsBool(const celix_properties_t *props, const char *key return result; } -void celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { - celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); +celix_status_t celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { + return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); } const celix_version_t* celix_properties_getVersion( @@ -776,8 +809,8 @@ celix_version_t* celix_properties_getAsVersion( return defaultValue == NULL ? NULL : celix_version_copy(defaultValue); } -void celix_properties_setVersion(celix_properties_t *properties, const char *key, const celix_version_t* version) { - celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); +celix_status_t celix_properties_setVersion(celix_properties_t *properties, const char *key, const celix_version_t* version) { + return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); } void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) { From 3541c67b2d4b475c4b62f827968e3c4fd11337df Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 17 Oct 2023 20:38:46 +0200 Subject: [PATCH 27/63] Refactor celix hash map for alloc failure handling --- CHANGES.md | 2 + libs/framework/gtest/CMakeLists.txt | 2 +- ...CelixBundleCacheErrorInjectionTestSuite.cc | 2 +- libs/utils/error_injector/CMakeLists.txt | 2 +- .../include/celix_hash_map_ei.h | 36 ----- .../celix_hash_map/src/celix_hash_map_ei.cc | 38 ----- .../celix_long_hash_map/CMakeLists.txt | 5 + .../include/celix_long_hash_map_ei.h | 11 ++ .../src/celix_long_hash_map_ei.cc | 35 +++++ .../CMakeLists.txt | 17 ++- .../include/celix_string_hash_map_ei.h | 44 ++++++ .../src/celix_string_hash_map_ei.cc | 58 +++++++ libs/utils/gtest/CMakeLists.txt | 4 +- .../src/HashMapErrorInjectionTestSuite.cc | 107 +++++++++++++ libs/utils/gtest/src/HashMapTestSuite.cc | 56 +++---- .../src/PropertiesErrorInjectionTestSuite.cc | 11 +- libs/utils/include/celix_long_hash_map.h | 33 ++-- libs/utils/include/celix_string_hash_map.h | 30 ++-- libs/utils/src/celix_hash_map.c | 141 +++++++++++------- libs/utils/src/celix_hash_map_private.h | 67 +++++++++ libs/utils/src/properties.c | 19 +-- 21 files changed, 512 insertions(+), 208 deletions(-) delete mode 100644 libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h delete mode 100644 libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc rename libs/utils/error_injector/{celix_hash_map => celix_string_hash_map}/CMakeLists.txt (62%) create mode 100644 libs/utils/error_injector/celix_string_hash_map/include/celix_string_hash_map_ei.h create mode 100644 libs/utils/error_injector/celix_string_hash_map/src/celix_string_hash_map_ei.cc create mode 100644 libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc create mode 100644 libs/utils/src/celix_hash_map_private.h diff --git a/CHANGES.md b/CHANGES.md index 8191a9052..29efb27c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,8 @@ limitations under the License. - C++17 string_view support is removed from the utils and framework lib. - pubsub_serializer.h is removed and no longer supported. Use pubsub_message_serialization_service.h instead. - C Properties are no longer a direct typedef o `hashmap`. +- celix_string/long_hashmap put functions now return a celix_status_t instead of bool (value replaced). + THe celix_status_t is used to indicate an ENOMEM error. ## New Features diff --git a/libs/framework/gtest/CMakeLists.txt b/libs/framework/gtest/CMakeLists.txt index 044659b88..e6697b94a 100644 --- a/libs/framework/gtest/CMakeLists.txt +++ b/libs/framework/gtest/CMakeLists.txt @@ -154,7 +154,7 @@ if (EI_TESTS) Celix::asprintf_ei Celix::dlfcn_ei Celix::unistd_ei - Celix::hash_map_ei + Celix::string_hash_map_ei Celix::properties_ei Celix::stdlib_ei Celix::stat_ei diff --git a/libs/framework/gtest/src/CelixBundleCacheErrorInjectionTestSuite.cc b/libs/framework/gtest/src/CelixBundleCacheErrorInjectionTestSuite.cc index 3e4d66d4c..2082013e4 100644 --- a/libs/framework/gtest/src/CelixBundleCacheErrorInjectionTestSuite.cc +++ b/libs/framework/gtest/src/CelixBundleCacheErrorInjectionTestSuite.cc @@ -24,7 +24,7 @@ #include "celix_bundle_cache.h" #include "celix_constants.h" #include "celix_file_utils.h" -#include "celix_hash_map_ei.h" +#include "celix_string_hash_map_ei.h" #include "celix_log.h" #include "celix_properties.h" #include "celix_utils_ei.h" diff --git a/libs/utils/error_injector/CMakeLists.txt b/libs/utils/error_injector/CMakeLists.txt index 9c43fac75..dcdd69d3d 100644 --- a/libs/utils/error_injector/CMakeLists.txt +++ b/libs/utils/error_injector/CMakeLists.txt @@ -16,8 +16,8 @@ # under the License. add_subdirectory(celix_array_list) -add_subdirectory(celix_hash_map) add_subdirectory(celix_long_hash_map) +add_subdirectory(celix_string_hash_map) add_subdirectory(celix_properties) add_subdirectory(celix_threads) add_subdirectory(celix_utils) diff --git a/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h b/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h deleted file mode 100644 index 5dbdd658d..000000000 --- a/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - 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. - */ - -#ifndef CELIX_CELIX_HASH_MAP_EI_H -#define CELIX_CELIX_HASH_MAP_EI_H -#ifdef __cplusplus -extern "C" { -#endif - -#include "celix_string_hash_map.h" -#include "celix_error_injector.h" - -CELIX_EI_DECLARE(celix_stringHashMap_create, celix_string_hash_map_t*); - -CELIX_EI_DECLARE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); - -#ifdef __cplusplus -} -#endif -#endif //CELIX_CELIX_HASH_MAP_EI_H diff --git a/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc b/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc deleted file mode 100644 index bbd3af6da..000000000 --- a/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - 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 "celix_hash_map_ei.h" - -extern "C" { - -celix_string_hash_map_t* __real_celix_stringHashMap_create(); -CELIX_EI_DEFINE(celix_stringHashMap_create, celix_string_hash_map_t*); -celix_string_hash_map_t* __wrap_celix_stringHashMap_create() { - CELIX_EI_IMPL(celix_stringHashMap_create); - return __real_celix_stringHashMap_create(); -} - -celix_string_hash_map_t* __real_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts); -CELIX_EI_DEFINE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); -celix_string_hash_map_t* __wrap_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts) { - CELIX_EI_IMPL(celix_stringHashMap_createWithOptions); - return __real_celix_stringHashMap_createWithOptions(opts); -} - -} diff --git a/libs/utils/error_injector/celix_long_hash_map/CMakeLists.txt b/libs/utils/error_injector/celix_long_hash_map/CMakeLists.txt index ad3cb09c3..addaaff75 100644 --- a/libs/utils/error_injector/celix_long_hash_map/CMakeLists.txt +++ b/libs/utils/error_injector/celix_long_hash_map/CMakeLists.txt @@ -22,5 +22,10 @@ target_link_libraries(long_hash_map_ei PUBLIC Celix::error_injector Celix::utils target_link_options(long_hash_map_ei INTERFACE LINKER:--wrap,celix_longHashMap_create + LINKER:--wrap,celix_longHashMap_createWithOptions + LINKER:--wrap,celix_longHashMap_put + LINKER:--wrap,celix_longHashMap_putLong + LINKER:--wrap,celix_longHashMap_putDouble + LINKER:--wrap,celix_longHashMap_putBool ) add_library(Celix::long_hash_map_ei ALIAS long_hash_map_ei) diff --git a/libs/utils/error_injector/celix_long_hash_map/include/celix_long_hash_map_ei.h b/libs/utils/error_injector/celix_long_hash_map/include/celix_long_hash_map_ei.h index 6b1b9e9c3..011cd30e1 100644 --- a/libs/utils/error_injector/celix_long_hash_map/include/celix_long_hash_map_ei.h +++ b/libs/utils/error_injector/celix_long_hash_map/include/celix_long_hash_map_ei.h @@ -24,8 +24,19 @@ extern "C" { #endif #include "celix_error_injector.h" #include "celix_long_hash_map.h" + CELIX_EI_DECLARE(celix_longHashMap_create, celix_long_hash_map_t*); +CELIX_EI_DECLARE(celix_longHashMap_createWithOptions, celix_long_hash_map_t*); + +CELIX_EI_DECLARE(celix_longHashMap_put, celix_status_t); + +CELIX_EI_DECLARE(celix_longHashMap_putLong, celix_status_t); + +CELIX_EI_DECLARE(celix_longHashMap_putDouble, celix_status_t); + +CELIX_EI_DECLARE(celix_longHashMap_putBool, celix_status_t); + #ifdef __cplusplus } #endif diff --git a/libs/utils/error_injector/celix_long_hash_map/src/celix_long_hash_map_ei.cc b/libs/utils/error_injector/celix_long_hash_map/src/celix_long_hash_map_ei.cc index 2c1fa65aa..c83ed97e6 100644 --- a/libs/utils/error_injector/celix_long_hash_map/src/celix_long_hash_map_ei.cc +++ b/libs/utils/error_injector/celix_long_hash_map/src/celix_long_hash_map_ei.cc @@ -27,4 +27,39 @@ celix_long_hash_map_t* __wrap_celix_longHashMap_create(void) { return __real_celix_longHashMap_create(); } +celix_long_hash_map_t* __real_celix_longHashMap_createWithOptions(const celix_long_hash_map_create_options_t* opts); +CELIX_EI_DEFINE(celix_longHashMap_createWithOptions, celix_long_hash_map_t*); +celix_long_hash_map_t* __wrap_celix_longHashMap_createWithOptions(const celix_long_hash_map_create_options_t* opts) { + CELIX_EI_IMPL(celix_longHashMap_createWithOptions); + return __real_celix_longHashMap_createWithOptions(opts); +} + +celix_status_t __real_celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value); +CELIX_EI_DEFINE(celix_longHashMap_put, celix_status_t) +celix_status_t __wrap_celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value) { + CELIX_EI_IMPL(celix_longHashMap_put); + return __real_celix_longHashMap_put(map, key, value); +} + +celix_status_t __real_celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value); +CELIX_EI_DEFINE(celix_longHashMap_putLong, celix_status_t) +celix_status_t __wrap_celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value) { + CELIX_EI_IMPL(celix_longHashMap_putLong); + return __real_celix_longHashMap_putLong(map, key, value); +} + +celix_status_t __real_celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value); +CELIX_EI_DEFINE(celix_longHashMap_putDouble, celix_status_t) +celix_status_t __wrap_celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value) { + CELIX_EI_IMPL(celix_longHashMap_putDouble); + return __real_celix_longHashMap_putDouble(map, key, value); +} + +celix_status_t __real_celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value); +CELIX_EI_DEFINE(celix_longHashMap_putBool, celix_status_t) +celix_status_t __wrap_celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value) { + CELIX_EI_IMPL(celix_longHashMap_putBool); + return __real_celix_longHashMap_putBool(map, key, value); +} + } \ No newline at end of file diff --git a/libs/utils/error_injector/celix_hash_map/CMakeLists.txt b/libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt similarity index 62% rename from libs/utils/error_injector/celix_hash_map/CMakeLists.txt rename to libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt index 19df3d4ac..651b7afa0 100644 --- a/libs/utils/error_injector/celix_hash_map/CMakeLists.txt +++ b/libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt @@ -15,12 +15,17 @@ # specific language governing permissions and limitations # under the License. -add_library(hash_map_ei STATIC src/celix_hash_map_ei.cc) +add_library(string_hash_map_ei STATIC src/celix_string_hash_map_ei.cc) -target_include_directories(hash_map_ei PUBLIC include) -target_link_libraries(hash_map_ei PUBLIC Celix::error_injector Celix::utils) -target_link_options(hash_map_ei INTERFACE +target_include_directories(string_hash_map_ei PUBLIC include) +target_link_libraries(string_hash_map_ei PUBLIC Celix::error_injector Celix::utils) + +target_link_options(string_hash_map_ei INTERFACE LINKER:--wrap,celix_stringHashMap_create LINKER:--wrap,celix_stringHashMap_createWithOptions -) -add_library(Celix::hash_map_ei ALIAS hash_map_ei) + LINKER:--wrap,celix_stringHashMap_put + LINKER:--wrap,celix_stringHashMap_putLong + LINKER:--wrap,celix_stringHashMap_putDouble + LINKER:--wrap,celix_stringHashMap_putBool + ) +add_library(Celix::string_hash_map_ei ALIAS string_hash_map_ei) diff --git a/libs/utils/error_injector/celix_string_hash_map/include/celix_string_hash_map_ei.h b/libs/utils/error_injector/celix_string_hash_map/include/celix_string_hash_map_ei.h new file mode 100644 index 000000000..ca45ff5bd --- /dev/null +++ b/libs/utils/error_injector/celix_string_hash_map/include/celix_string_hash_map_ei.h @@ -0,0 +1,44 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_LONG_HASH_MAP_EI_H +#define CELIX_CELIX_LONG_HASH_MAP_EI_H +#ifdef __cplusplus +extern "C" { +#endif +#include "celix_error_injector.h" +#include "celix_string_hash_map.h" + +CELIX_EI_DECLARE(celix_stringHashMap_create, celix_string_hash_map_t*); + +CELIX_EI_DECLARE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); + +CELIX_EI_DECLARE(celix_stringHashMap_put, celix_status_t); + +CELIX_EI_DECLARE(celix_stringHashMap_putLong, celix_status_t); + +CELIX_EI_DECLARE(celix_stringHashMap_putDouble, celix_status_t); + +CELIX_EI_DECLARE(celix_stringHashMap_putBool, celix_status_t); + +#ifdef __cplusplus +} +#endif + +#endif //CELIX_CELIX_LONG_HASH_MAP_EI_H diff --git a/libs/utils/error_injector/celix_string_hash_map/src/celix_string_hash_map_ei.cc b/libs/utils/error_injector/celix_string_hash_map/src/celix_string_hash_map_ei.cc new file mode 100644 index 000000000..b63417feb --- /dev/null +++ b/libs/utils/error_injector/celix_string_hash_map/src/celix_string_hash_map_ei.cc @@ -0,0 +1,58 @@ +/* + * 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 "celix_string_hash_map_ei.h" + +extern "C" { + +celix_string_hash_map_t* __real_celix_stringHashMap_create(void); +CELIX_EI_DEFINE(celix_stringHashMap_create, celix_string_hash_map_t*) +celix_string_hash_map_t* __wrap_celix_stringHashMap_create(void) { + CELIX_EI_IMPL(celix_stringHashMap_create); + return __real_celix_stringHashMap_create(); +} + +celix_string_hash_map_t* __real_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts); +CELIX_EI_DEFINE(celix_stringHashMap_createWithOptions, celix_string_hash_map_t*); +celix_string_hash_map_t* __wrap_celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts) { + CELIX_EI_IMPL(celix_stringHashMap_createWithOptions); + return __real_celix_stringHashMap_createWithOptions(opts); +} + +celix_status_t __real_celix_stringHashMap_put(celix_string_hash_map_t* map, long key, void* value); +CELIX_EI_DEFINE(celix_stringHashMap_put, celix_status_t) +celix_status_t __wrap_celix_stringHashMap_put(celix_string_hash_map_t* map, long key, void* value) { + CELIX_EI_IMPL(celix_stringHashMap_put); + return __real_celix_stringHashMap_put(map, key, value); +} + +celix_status_t __real_celix_stringHashMap_putLong(celix_string_hash_map_t* map, long key, long value); +CELIX_EI_DEFINE(celix_stringHashMap_putLong, celix_status_t) +celix_status_t __wrap_celix_stringHashMap_putLong(celix_string_hash_map_t* map, long key, long value) { + CELIX_EI_IMPL(celix_stringHashMap_putLong); + return __real_celix_stringHashMap_putLong(map, key, value); +} + +celix_status_t __real_celix_stringHashMap_putDouble(celix_string_hash_map_t* map, long key, double value); +CELIX_EI_DEFINE(celix_stringHashMap_putDouble, celix_status_t) +celix_status_t __wrap_celix_stringHashMap_putDouble(celix_string_hash_map_t* map, long key, double value) { + CELIX_EI_IMPL(celix_stringHashMap_putDouble); + return __real_celix_stringHashMap_putDouble(map, key, value); +} + +} \ No newline at end of file diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 32fbc2c76..4899cc895 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -98,6 +98,7 @@ if (EI_TESTS) src/ErrErrorInjectionTestSuite.cc src/PropertiesErrorInjectionTestSuite.cc src/VersionErrorInjectionTestSuite.cc + src/HashMapErrorInjectionTestSuite.cc ) target_link_libraries(test_utils_with_ei PRIVATE Celix::zip_ei @@ -109,7 +110,8 @@ if (EI_TESTS) Celix::ifaddrs_ei Celix::threads_ei Celix::malloc_ei - Celix::hash_map_ei + Celix::string_hash_map_ei + Celix::long_hash_map_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/HashMapErrorInjectionTestSuite.cc b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc new file mode 100644 index 000000000..956d01497 --- /dev/null +++ b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc @@ -0,0 +1,107 @@ +/* + * 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 + +#include + +#include "celix_err.h" +#include "celix_hash_map_private.h" +#include "celix_long_hash_map.h" +#include "celix_string_hash_map.h" + +#include "malloc_ei.h" + +class HashMapErrorInjectionTestSuite : public ::testing::Test { + public: + HashMapErrorInjectionTestSuite() { celix_ei_expect_calloc(nullptr, 0, nullptr); } +}; + +TEST_F(HashMapErrorInjectionTestSuite, CreateFailureTest) { + // When a calloc error injection is set for celix_hashMap_init + celix_ei_expect_calloc((void*)celix_hashMap_init, 0, nullptr); + // Then celix_stringHashMap_create will return nullptr + auto* sProps = celix_stringHashMap_create(); + ASSERT_EQ(nullptr, sProps); + + // When a calloc error injection is set for celix_hashMap_init + celix_ei_expect_calloc((void*)celix_hashMap_init, 0, nullptr); + // Then celix_stringHashMap_create will return nullptr + sProps = celix_stringHashMap_create(); + ASSERT_EQ(nullptr, sProps); + + // When a calloc error injection is set for celix_hashMap_init + celix_ei_expect_calloc((void*)celix_hashMap_init, 0, nullptr); + // Then celix_longHashMap_create will return nullptr + auto* lProps = celix_longHashMap_create(); + ASSERT_EQ(nullptr, lProps); + + // When a calloc error injection is set for celix_hashMap_init + celix_ei_expect_calloc((void*)celix_hashMap_init, 0, nullptr); + // Then celix_longHashMap_create will return nullptr + lProps = celix_longHashMap_create(); + ASSERT_EQ(nullptr, lProps); + + EXPECT_EQ(celix_err_getErrorCount(), 4); // 4x calloc error + celix_err_resetErrors(); +} + +TEST_F(HashMapErrorInjectionTestSuite, PutFailureTest) { + // Given a celix_string_hash_map_t + celix_autoptr(celix_string_hash_map_t) sProps = celix_stringHashMap_create(); + ASSERT_NE(nullptr, sProps); + + // When a malloc error injection is set for celix_hashMap_addEntry + celix_ei_expect_malloc((void*)celix_hashMap_addEntry, 0, nullptr); + // Then celix_stringHashMap_putLong will return CELIX_ENOMEM + auto status = celix_stringHashMap_putLong(sProps, "key", 1L); + ASSERT_EQ(CELIX_ENOMEM, status); + + // Given a celix_long_hash_map_t + celix_autoptr(celix_long_hash_map_t) lProps = celix_longHashMap_create(); + ASSERT_NE(nullptr, lProps); + + // When a malloc error injection is set for celix_hashMap_addEntry + celix_ei_expect_malloc((void*)celix_hashMap_addEntry, 0, nullptr); + // Then celix_stringHashMap_putLong will return CELIX_ENOMEM + status = celix_longHashMap_putLong(lProps, 1, 1L); + ASSERT_EQ(CELIX_ENOMEM, status); + + EXPECT_EQ(celix_err_getErrorCount(), 2); // 2x malloc error + celix_err_resetErrors(); +} + +TEST_F(HashMapErrorInjectionTestSuite, ResizeFailureTest) { + // Given a hashmap with a low load factor + celix_long_hash_map_create_options_t opts{}; + opts.loadFactor = 0.1; + celix_autoptr(celix_long_hash_map_t) lProps = celix_longHashMap_createWithOptions(&opts); + + // And when the hash map is filled 1 entry before the resize threshold + celix_longHashMap_putLong(lProps, 0, 0); + + // When a realloc error injection is set for celix_hashMap_resize + celix_ei_expect_realloc((void*)celix_hashMap_resize, 0, nullptr); + // Then celix_stringHashMap_putLong will return CELIX_ENOMEM + auto status = celix_longHashMap_putLong(lProps, 1, 1L); + ASSERT_EQ(CELIX_ENOMEM, status); + + EXPECT_EQ(celix_err_getErrorCount(), 1); // 2x realloc error + celix_err_resetErrors(); +} diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 24ac9a0fd..c6f3cfb8c 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -293,45 +293,29 @@ void testMapsWithValues(const std::vector>& val //test hashmap put for (const auto& tuple : values) { if constexpr (std::is_same_v) { - void* prev = celix_stringHashMap_put(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_EQ(prev, nullptr); - prev = celix_stringHashMap_put(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_EQ(prev, std::get<2>(tuple)); - - prev = celix_longHashMap_put(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_EQ(prev, nullptr); - prev = celix_longHashMap_put(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_EQ(prev, std::get<2>(tuple)); + auto status = celix_stringHashMap_put(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_longHashMap_put(lMap, std::get<1>(tuple), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); } else if constexpr (std::is_same_v) { - bool replaced = celix_stringHashMap_putLong(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_stringHashMap_putLong(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_TRUE(replaced); - - replaced = celix_longHashMap_putLong(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_longHashMap_putLong(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_TRUE(replaced); + auto status = celix_stringHashMap_putLong(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_longHashMap_putLong(lMap, std::get<1>(tuple), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); } else if constexpr (std::is_same_v) { - bool replaced = celix_stringHashMap_putDouble(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_stringHashMap_putDouble(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_TRUE(replaced); - - replaced = celix_longHashMap_putDouble(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_longHashMap_putDouble(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_TRUE(replaced); + auto status = celix_stringHashMap_putDouble(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_longHashMap_putDouble(lMap, std::get<1>(tuple), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); } else if constexpr (std::is_same_v) { - bool replaced = celix_stringHashMap_putBool(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_stringHashMap_putBool(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); - EXPECT_TRUE(replaced); - - replaced = celix_longHashMap_putBool(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_FALSE(replaced); - replaced = celix_longHashMap_putBool(lMap, std::get<1>(tuple), std::get<2>(tuple)); - EXPECT_TRUE(replaced); + auto status = celix_stringHashMap_putBool(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); + + status = celix_longHashMap_putBool(lMap, std::get<1>(tuple), std::get<2>(tuple)); + EXPECT_EQ(status, CELIX_SUCCESS); } else { FAIL() << "expected of the following value types: void*, long, double or bool"; } diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 5130e8e8a..d3ad87578 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -25,7 +25,7 @@ #include "celix/Properties.h" #include "malloc_ei.h" -#include "celix_hash_map_ei.h" +#include "celix_string_hash_map_ei.h" #include "celix_utils_ei.h" class PropertiesErrorInjectionTestSuite : public ::testing::Test { @@ -33,7 +33,7 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { PropertiesErrorInjectionTestSuite() = default; ~PropertiesErrorInjectionTestSuite() override { celix_ei_expect_malloc(nullptr, 0, nullptr); - celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_create(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } }; @@ -74,7 +74,7 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { celix_properties_set(props, key, val); } - //When a malloc error injection is set for celix_properties_set with level 2 (during alloc entry) + //When a malloc error injection is set for celix_properties_set with level 3 (during alloc entry) celix_ei_expect_malloc((void *)celix_properties_set, 3, nullptr); //Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); @@ -84,6 +84,11 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { //Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); + //When a celix_stringHashMap_put error injection is set for celix_properties_set with level 1 (during put) + celix_ei_expect_celix_stringHashMap_put((void *)celix_properties_set, 1, CELIX_ENOMEM); + //Then the celix_properties_set call fails + ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); + //C++ API //Given a celix properties object with a filled optimization cache celix::Properties cxxProps{}; diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 9be05da31..87aac9a14 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -23,6 +23,7 @@ #include "celix_cleanup.h" #include "celix_hash_map_value.h" #include "celix_utils_export.h" +#include "celix_errno.h" /** * @brief Init macro so that the opts are correctly initialized for C++ compilers @@ -173,52 +174,62 @@ CELIX_UTILS_EXPORT size_t celix_longHashMap_size(const celix_long_hash_map_t* ma /** * @brief Add pointer entry the string hash map. * - * @note The returned previous value can be already freed by a removed callback (if configured). + * If the return status is an error, a error message is logged to celix_err. * * @param[in] map The hashmap * @param[in] key The key to use. * @param[in] value The value to store with the key - * @return The previous value or NULL of no value was set for th provided key. - * Note also returns NULL if the previous value for the key was NULL. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT void* celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value); +CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value); /** * @brief add long entry the long hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap * @param key The key to use. * @param value The value to store with the key. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value); +CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value); /** * @brief add double entry the long hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap * @param key The key to use. * @param value The value to store with the key. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value); +CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value); /** * @brief add bool entry the long hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap * @param key The key to use. * @param value The value to store with the key. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value); +CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value); /** * @brief Returns the value for the provided key. * * @param map The hashmap. * @param key The key to lookup. - * @return Return the pointer value for the key or NULL. Note will also return NULL if the pointer value for the provided key is NULL. + * @return Return the pointer value for the key or NULL. Note will also return NULL if the pointer value for + * the provided key is NULL. */ CELIX_UTILS_EXPORT void* celix_longHashMap_get(const celix_long_hash_map_t* map, long key); diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 81ab0ba26..1097b109a 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -23,6 +23,7 @@ #include "celix_cleanup.h" #include "celix_hash_map_value.h" #include "celix_utils_export.h" +#include "celix_errno.h" /** * @brief Init macro so that the opts are correctly initialized for C++ compilers @@ -202,44 +203,53 @@ CELIX_UTILS_EXPORT size_t celix_stringHashMap_size(const celix_string_hash_map_t /** * @brief Add pointer entry the string hash map. * - * @note The returned previous value can be already freed by a removed callback (if configured). + * If the return status is an error, a error message is logged to celix_err. * * @param[in] map The hashmap. * @param[in] key The key to use. The hashmap will create a copy if needed. * @param[in] value The value to store with the key. - * @return The previous value or NULL of no value was set for th provided key. - * Note also returns NULL if the previous value for the key was NULL. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT void* celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value); +CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value); /** * @brief add long entry the string hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. * @param value The value to store with the key. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long value); +CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long value); /** * @brief add double entry the string hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_stringHashMap_putDouble(celix_string_hash_map_t* map, const char* key, double value); +CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_putDouble(celix_string_hash_map_t* map, const char* key, double value); /** * @brief add bool entry the string hash map. * + * If the return status is an error, a error message is logged to celix_err. + * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. * @param value The value to store with the key. - * @return True if a previous value with the provided has been replaced. + * @return celix_status_t CELIX_SUCCESS if the entry was added, CELIX_ENOMEM if no memory could be allocated for the + * entry. */ -CELIX_UTILS_EXPORT bool celix_stringHashMap_putBool(celix_string_hash_map_t* map, const char* key, bool value); +CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_putBool(celix_string_hash_map_t* map, const char* key, bool value); /** * @brief Returns the value for the provided key. diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index e6113754c..f59d2fe32 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -19,7 +19,7 @@ #include "celix_string_hash_map.h" #include "celix_long_hash_map.h" -#include "celix_utils.h" +#include "celix_hash_map_private.h" #include #include @@ -27,21 +27,22 @@ #include #include +#include "celix_utils.h" +#include "celix_err.h" +#include "celix_stdlib_cleanup.h" + static unsigned int DEFAULT_INITIAL_CAPACITY = 16; static double DEFAULT_LOAD_FACTOR = 0.75; static unsigned int MAXIMUM_CAPACITY = INT32_MAX/10; -typedef enum celix_hash_map_key_type { - CELIX_HASH_MAP_STRING_KEY, - CELIX_HASH_MAP_LONG_KEY -} celix_hash_map_key_type_e; -typedef union celix_hash_map_key { + +union celix_hash_map_key { const char* strKey; long longKey; -} celix_hash_map_key_t; +}; + -typedef struct celix_hash_map_entry celix_hash_map_entry_t; struct celix_hash_map_entry { celix_hash_map_key_t key; celix_hash_map_value_t value; @@ -49,7 +50,7 @@ struct celix_hash_map_entry { unsigned int hash; }; -typedef struct celix_hash_map { +struct celix_hash_map { celix_hash_map_entry_t** buckets; unsigned int bucketsSize; //nr of buckets unsigned int size; //nr of total entries @@ -64,7 +65,7 @@ typedef struct celix_hash_map { void (*removedStringKeyCallback)(void* data, char* key); void (*removedLongEntryCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue); bool storeKeysWeakly; -} celix_hash_map_t; +}; struct celix_string_hash_map { celix_hash_map_t genericMap; @@ -151,26 +152,33 @@ bool celix_hashMap_hasKey(const celix_hash_map_t* map, const char* strKey, long return celix_hashMap_getEntry(map, strKey, longKey) != NULL; } -static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { - if (map->bucketsSize == MAXIMUM_CAPACITY) { - return; +celix_status_t celix_hashMap_resize(celix_hash_map_t *map, size_t newCapacity) { + if (map->bucketsSize >= MAXIMUM_CAPACITY) { + return CELIX_SUCCESS; } assert(newCapacity > map->bucketsSize); //hash map can only grow - map->buckets = realloc(map->buckets, newCapacity * sizeof(celix_hash_map_entry_t*)); + + celix_hash_map_entry_t **newBuf = realloc(map->buckets, newCapacity * sizeof(celix_hash_map_entry_t *)); + if (!newBuf) { + celix_err_push("Cannot realloc memory for hash map"); + return CELIX_ENOMEM; + } + + map->buckets = newBuf; //clear newly added mem for (unsigned int i = map->bucketsSize; i < newCapacity; ++i) { - map->buckets[i]= NULL; + map->buckets[i] = NULL; } //reinsert old entries for (unsigned i = 0; i < map->bucketsSize; i++) { - celix_hash_map_entry_t* entry = map->buckets[i]; + celix_hash_map_entry_t *entry = map->buckets[i]; if (entry != NULL) { map->buckets[i] = NULL; do { - celix_hash_map_entry_t* next = entry->next; + celix_hash_map_entry_t *next = entry->next; unsigned int bucketIndex = celix_hashMap_indexFor(entry->hash, newCapacity); entry->next = map->buckets[bucketIndex]; map->buckets[bucketIndex] = entry; @@ -181,6 +189,8 @@ static void celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity) { //update bucketSize to new capacity map->bucketsSize = newCapacity; + + return CELIX_SUCCESS; } static void celix_hashMap_callRemovedCallback(celix_hash_map_t* map, celix_hash_map_entry_t* removedEntry) { @@ -207,9 +217,14 @@ static void celix_hashMap_destroyRemovedEntry(celix_hash_map_t* map, celix_hash_ free(removedEntry); } -static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value, unsigned int bucketIndex) { +celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value, unsigned int bucketIndex) { celix_hash_map_entry_t* entry = map->buckets[bucketIndex]; celix_hash_map_entry_t* newEntry = malloc(sizeof(*newEntry)); + if (!newEntry) { + celix_err_push("Cannot allocate memory for hash map entry"); + return CELIX_ENOMEM; + } + newEntry->hash = hash; if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { newEntry->key.strKey = map->storeKeysWeakly ? key->strKey : celix_utils_strdup(key->strKey); @@ -220,11 +235,12 @@ static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, con newEntry->next = entry; map->buckets[bucketIndex] = newEntry; if (map->size++ >= celix_hashMap_threshold(map)) { - celix_hashMap_resize(map, 2 * map->bucketsSize); + return celix_hashMap_resize(map, 2 * map->bucketsSize); } + return CELIX_SUCCESS; } -static bool celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, long longKey, const celix_hash_map_value_t* value, celix_hash_map_value_t* replacedValueOut) { +static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, long longKey, const celix_hash_map_value_t* value, celix_hash_map_value_t* replacedValueOut) { celix_hash_map_key_t key; if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { key.strKey = strKey; @@ -241,37 +257,35 @@ static bool celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, lo } celix_hashMap_callRemovedCallback(map, entry); memcpy(&entry->value, value, sizeof(*value)); - return true; + return CELIX_SUCCESS; } } - celix_hashMap_addEntry(map, hash, &key, value, index); - if (replacedValueOut != NULL) { + celix_status_t status = celix_hashMap_addEntry(map, hash, &key, value, index); + if (status == CELIX_SUCCESS && replacedValueOut != NULL) { memset(replacedValueOut, 0, sizeof(*replacedValueOut)); } - return false; + return status; } -static void* celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v) { +static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v, void** previousValueOut) { celix_hash_map_value_t value; value.ptrValue = v; - celix_hash_map_value_t replaced; - celix_hashMap_putValue(map, strKey, longKey, &value, &replaced); - return replaced.ptrValue; + return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static bool celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v) { +static celix_status_t celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v, long* previousValueOut) { celix_hash_map_value_t value; value.longValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static bool celix_hashMap_putDouble(celix_hash_map_t* map, const char* strKey, long longKey, double v) { +static celix_status_t celix_hashMap_putDouble(celix_hash_map_t* map, const char* strKey, long longKey, double v) { celix_hash_map_value_t value; value.doubleValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static bool celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v) { +static celix_status_t celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v, bool* previousValueOut) { celix_hash_map_value_t value; value.boolValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); @@ -322,7 +336,7 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long return false; } -static void celix_hashMap_init( +celix_status_t celix_hashMap_init( celix_hash_map_t* map, celix_hash_map_key_type_e keyType, unsigned int initialCapacity, @@ -330,7 +344,6 @@ static void celix_hashMap_init( unsigned int (*hashKeyFn)(const celix_hash_map_key_t*), bool (*equalsKeyFn)(const celix_hash_map_key_t*, const celix_hash_map_key_t*)) { map->loadFactor = loadFactor; - map->buckets = calloc(initialCapacity, sizeof(celix_hash_map_entry_t*)); map->size = 0; map->bucketsSize = initialCapacity; map->keyType = keyType; @@ -343,6 +356,9 @@ static void celix_hashMap_init( map->removedStringEntryCallback = NULL; map->removedStringKeyCallback = NULL; map->storeKeysWeakly = false; + + map->buckets = calloc(initialCapacity, sizeof(celix_hash_map_entry_t*)); + return map->buckets == NULL ? CELIX_ENOMEM : CELIX_SUCCESS; } static void celix_hashMap_clear(celix_hash_map_t* map) { @@ -402,16 +418,26 @@ static celix_hash_map_entry_t* celix_hashMap_nextEntry(const celix_hash_map_t* m celix_string_hash_map_t* celix_stringHashMap_createWithOptions(const celix_string_hash_map_create_options_t* opts) { - celix_string_hash_map_t* map = calloc(1, sizeof(*map)); + celix_autofree celix_string_hash_map_t* map = calloc(1, sizeof(*map)); + if (!map) { + celix_err_push("Cannot allocate memory for hash map"); + return NULL; + } + unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; map->genericMap.removedCallbackData = opts->removedCallbackData; map->genericMap.removedStringEntryCallback = opts->removedCallback; map->genericMap.removedStringKeyCallback = opts->removedKeyCallback; map->genericMap.storeKeysWeakly = opts->storeKeysWeakly; - return map; + + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); + if (status != CELIX_SUCCESS) { + celix_err_push("Cannot initialize hash map"); + return NULL; + } + return celix_steal_ptr(map); } celix_string_hash_map_t* celix_stringHashMap_create() { @@ -420,15 +446,24 @@ celix_string_hash_map_t* celix_stringHashMap_create() { } celix_long_hash_map_t* celix_longHashMap_createWithOptions(const celix_long_hash_map_create_options_t* opts) { - celix_long_hash_map_t* map = calloc(1, sizeof(*map)); + celix_autofree celix_long_hash_map_t* map = calloc(1, sizeof(*map)); + if (!map) { + celix_err_push("Cannot allocate memory for hash map"); + return NULL; + } unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_equals); map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; map->genericMap.removedCallbackData = opts->removedCallbackData; map->genericMap.removedLongEntryCallback = opts->removedCallback; map->genericMap.storeKeysWeakly = false; - return map; + + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_equals); + if (status != CELIX_SUCCESS) { + celix_err_push("Cannot initialize hash map"); + return NULL; + } + return celix_steal_ptr(map); } celix_long_hash_map_t* celix_longHashMap_create() { @@ -493,36 +528,36 @@ bool celix_longHashMap_getBool(const celix_long_hash_map_t* map, long key, bool return celix_hashMap_getBool(&map->genericMap, NULL, key, fallbackValue); } -void* celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value) { - return celix_hashMap_put(&map->genericMap, key, 0, value); +celix_status_t celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value) { + return celix_hashMap_put(&map->genericMap, key, 0, value, NULL); } -void* celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value) { - return celix_hashMap_put(&map->genericMap, NULL, key, value); +celix_status_t celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value) { + return celix_hashMap_put(&map->genericMap, NULL, key, value, NULL); } -bool celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long value) { - return celix_hashMap_putLong(&map->genericMap, key, 0, value); +celix_status_t celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long value) { + return celix_hashMap_putLong(&map->genericMap, key, 0, value, NULL); } -bool celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value) { - return celix_hashMap_putLong(&map->genericMap, NULL, key, value); +celix_status_t celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value) { + return celix_hashMap_putLong(&map->genericMap, NULL, key, value, NULL); } -bool celix_stringHashMap_putDouble(celix_string_hash_map_t* map, const char* key, double value) { +celix_status_t celix_stringHashMap_putDouble(celix_string_hash_map_t* map, const char* key, double value) { return celix_hashMap_putDouble(&map->genericMap, key, 0, value); } -bool celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value) { +celix_status_t celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, double value) { return celix_hashMap_putDouble(&map->genericMap, NULL, key, value); } -bool celix_stringHashMap_putBool(celix_string_hash_map_t* map, const char* key, bool value) { - return celix_hashMap_putBool(&map->genericMap, key, 0, value); +celix_status_t celix_stringHashMap_putBool(celix_string_hash_map_t* map, const char* key, bool value) { + return celix_hashMap_putBool(&map->genericMap, key, 0, value, NULL); } -bool celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value) { - return celix_hashMap_putBool(&map->genericMap, NULL, key, value); +celix_status_t celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value) { + return celix_hashMap_putBool(&map->genericMap, NULL, key, value, NULL); } bool celix_stringHashMap_hasKey(const celix_string_hash_map_t* map, const char* key) { diff --git a/libs/utils/src/celix_hash_map_private.h b/libs/utils/src/celix_hash_map_private.h new file mode 100644 index 000000000..9ad261f5c --- /dev/null +++ b/libs/utils/src/celix_hash_map_private.h @@ -0,0 +1,67 @@ +/** + *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. + */ +#ifndef CELIX_CELIX_HASH_MAP_PRIVATE_H +#define CELIX_CELIX_HASH_MAP_PRIVATE_H + +#include "celix_errno.h" +#include "celix_hash_map_value.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @file Private header for celix_hash_map, with function used for whitebox testing. + */ + +typedef struct celix_hash_map celix_hash_map_t; // opaque +typedef struct celix_hash_map_entry celix_hash_map_entry_t; // opaque +typedef union celix_hash_map_key celix_hash_map_key_t; // opaque + +typedef enum celix_hash_map_key_type { CELIX_HASH_MAP_STRING_KEY, CELIX_HASH_MAP_LONG_KEY } celix_hash_map_key_type_e; + +/** + * @brief Resizes the hash map to the given capacity using realloc. + */ +celix_status_t celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity); + +/** + * @brief Add a new entry to the hash map. + */ +celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, + unsigned int hash, + const celix_hash_map_key_t* key, + const celix_hash_map_value_t* value, + unsigned int bucketIndex); + +/** + * @brief Initialize the hash map. + */ +celix_status_t celix_hashMap_init(celix_hash_map_t* map, + celix_hash_map_key_type_e keyType, + unsigned int initialCapacity, + double loadFactor, + unsigned int (*hashKeyFn)(const celix_hash_map_key_t*), + bool (*equalsKeyFn)(const celix_hash_map_key_t*, const celix_hash_map_key_t*)); + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_HASH_MAP_PRIVATE_H diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 04bb63f2c..97a9a87ca 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -349,17 +349,14 @@ static celix_status_t celix_properties_createAndSetEntry( } } - //TODO -// celix_status_t status = celix_stringHashMap_put(properties->map, mapKey, entry); -// if (status != CELIX_SUCCESS) { -// celix_properties_destroyEntry(properties, entry); -// if (mapKey != key) { -// celix_properties_freeString(properties, (char*)mapKey); -// } -// } - - celix_stringHashMap_put(properties->map, mapKey, entry); - return CELIX_SUCCESS; + celix_status_t status = celix_stringHashMap_put(properties->map, mapKey, entry); + if (status != CELIX_SUCCESS) { + celix_properties_destroyEntry(properties, entry); + if (mapKey != key) { + celix_properties_freeString(properties, (char*)mapKey); + } + } + return status; } static void celix_properties_removeKeyCallback(void* handle, char* key) { From 94e6a3bd533f5ba84d190c78eb6e040f3d08643b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Tue, 17 Oct 2023 23:28:51 +0200 Subject: [PATCH 28/63] Fix some memleaks in properties.c --- .../src/celix_properties_ei.cc | 4 +- libs/utils/include/celix_properties.h | 130 +++---- libs/utils/src/celix_hash_map.c | 25 +- libs/utils/src/properties.c | 318 +++++++++--------- 4 files changed, 239 insertions(+), 238 deletions(-) diff --git a/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc b/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc index 9b47aed7a..eea81d814 100644 --- a/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc +++ b/libs/utils/error_injector/celix_properties/src/celix_properties_ei.cc @@ -20,9 +20,9 @@ #include "celix_properties_ei.h" extern "C" { -void *__real_celix_properties_create(void); +void *__real_celix_properties_create(); CELIX_EI_DEFINE(celix_properties_create, celix_properties_t*) -void *__wrap_celix_properties_create(void) { +void *__wrap_celix_properties_create() { CELIX_EI_IMPL(celix_properties_create); return __real_celix_properties_create(); } diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index f751ca743..144cd7b1b 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -40,8 +40,8 @@ #include "celix_cleanup.h" #include "celix_compiler.h" #include "celix_errno.h" -#include "celix_version.h" #include "celix_utils_export.h" +#include "celix_version.h" #ifndef CELIX_PROPERTIES_H_ #define CELIX_PROPERTIES_H_ @@ -61,32 +61,32 @@ typedef struct celix_properties celix_properties_t; * @brief Enum representing the possible types of a property value. */ typedef enum celix_properties_value_type { - CELIX_PROPERTIES_VALUE_TYPE_UNSET = 0, /**< Property value is not set. */ - CELIX_PROPERTIES_VALUE_TYPE_STRING = 1, /**< Property value is a string. */ - CELIX_PROPERTIES_VALUE_TYPE_LONG = 2, /**< Property value is a long integer. */ - CELIX_PROPERTIES_VALUE_TYPE_DOUBLE = 3, /**< Property value is a double. */ - CELIX_PROPERTIES_VALUE_TYPE_BOOL = 4, /**< Property value is a boolean. */ - CELIX_PROPERTIES_VALUE_TYPE_VERSION = 5 /**< Property value is a Celix version. */ + CELIX_PROPERTIES_VALUE_TYPE_UNSET = 0, /**< Property value is not set. */ + CELIX_PROPERTIES_VALUE_TYPE_STRING = 1, /**< Property value is a string. */ + CELIX_PROPERTIES_VALUE_TYPE_LONG = 2, /**< Property value is a long integer. */ + CELIX_PROPERTIES_VALUE_TYPE_DOUBLE = 3, /**< Property value is a double. */ + CELIX_PROPERTIES_VALUE_TYPE_BOOL = 4, /**< Property value is a boolean. */ + CELIX_PROPERTIES_VALUE_TYPE_VERSION = 5 /**< Property value is a Celix version. */ } celix_properties_value_type_e; /** * @brief A structure representing a single value entry in a property set. */ typedef struct celix_properties_entry { - const char* value; /**< The string value or string representation of a non-string - typed value.*/ - celix_properties_value_type_e valueType; /**< The type of the value of the entry */ + const char* value; /**< The string value or string representation of a non-string + typed value.*/ + celix_properties_value_type_e valueType; /**< The type of the value of the entry */ union { - const char* strValue; /**< The string value of the entry. */ - long longValue; /**< The long integer value of the entry. */ - double doubleValue; /**< The double-precision floating point value of the entry. */ - bool boolValue; /**< The boolean value of the entry. */ - celix_version_t* versionValue; /**< The Celix version value of the entry. */ - } typed; /**< The typed values of the entry. Only valid if valueType - is not CELIX_PROPERTIES_VALUE_TYPE_UNSET and only the matching - value types should be used. E.g typed.boolValue if valueType is - CELIX_PROPERTIES_VALUE_TYPE_BOOL. */ + const char* strValue; /**< The string value of the entry. */ + long longValue; /**< The long integer value of the entry. */ + double doubleValue; /**< The double-precision floating point value of the entry. */ + bool boolValue; /**< The boolean value of the entry. */ + const celix_version_t* versionValue; /**< The Celix version value of the entry. */ + } typed; /**< The typed values of the entry. Only valid if valueType + is not CELIX_PROPERTIES_VALUE_TYPE_UNSET and only the matching + value types should be used. E.g typed.boolValue if valueType is + CELIX_PROPERTIES_VALUE_TYPE_BOOL. */ } celix_properties_entry_t; /** @@ -119,7 +119,7 @@ typedef struct celix_properties_iterator { * * @return A new empty property set. */ -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(); /** * @brief Destroy a property set, freeing all associated resources. @@ -137,8 +137,7 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_properties_t, celix_properties_destroy) * @return A property set containing the properties from the file. * @retval NULL If an error occurred (e.g. file not found). */ -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char *filename); - +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filename); /** * @brief Load properties from a stream. @@ -147,7 +146,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char *filenam * @return A property set containing the properties from the stream. * @retval NULL If an error occurred (e.g. invalid format). */ -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream); /** * @brief Load properties from a string. @@ -156,7 +155,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *str * @return A property set containing the properties from the string. * @retval NULL If an error occurred (e.g. invalid format). */ -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input); +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input); /** * @brief Store properties to a file. @@ -170,7 +169,9 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const cha * @return CELIX_SUCCESS if the operation was successful, CELIX_FILE_IO_EXCEPTION if there was an error writing to the * file. */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_store(celix_properties_t* properties, const char* file, const char* header); +CELIX_UTILS_EXPORT celix_status_t celix_properties_store(celix_properties_t* properties, + const char* file, + const char* header); /** * @brief Get the entry for a given key in a property set. @@ -179,7 +180,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_store(celix_properties_t* pro * @param[in] key The key to search for. * @return The entry for the given key, or a NULL if the key is not found. */ -CELIX_UTILS_EXPORT celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, const char* key); +CELIX_UTILS_EXPORT celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* properties, + const char* key); /** * @brief Get the value of a property. @@ -188,8 +190,9 @@ CELIX_UTILS_EXPORT celix_properties_entry_t* celix_properties_getEntry(const cel * @param[in] key The key of the property to get. * @param[in] defaultValue The value to return if the property is not set. * @return The value of the property, or the default value if the property is not set. - */ -CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); + */ +CELIX_UTILS_EXPORT const char* +celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue); /** * @brief Get the type of a property value. @@ -198,7 +201,8 @@ CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t* pr * @param[in] key The key of the property to get the type of. * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set. */ -CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key); +CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, + const char* key); /** * @brief Set the value of a property. @@ -208,20 +212,22 @@ CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const * @param[in] value The value to set the property to. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char *value); +CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties, + const char* key, + const char* value); /** - * @brief Set the value of a property without copying the key and value strings. - * - * @note If the setWithoutCopy replaced an exising value, the key will be freed by callee. + * @brief Set the value of a property without copying the value string. * * @param[in] properties The property set to modify. - * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or modified - * after calling this function. + * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or +* modified after calling this function. * @param[in] value The value to set the property to. This string will be used directly, so it must not be freed or - * modified after calling this function. + * modified after calling this function. */ -CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char *value); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, + char* key, + char* value); /** * @brief Set the value of a property based on the provided property entry, maintaining underlying type. @@ -231,7 +237,9 @@ CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t* prop * this function. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* properties, + const char* key, + const celix_properties_entry_t* entry); /** * @brief Unset a property, removing it from the property set. @@ -239,7 +247,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* * @param[in] properties The property set to modify. * @param[in] key The key of the property to unset. */ -CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char *key); +CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char* key); /** * @brief Make a copy of a property set. @@ -260,7 +268,8 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_propert * the value is not a long integer, or if the value cannot be converted to a long integer. * If the value is a string, it will be converted to a long integer if possible. */ -CELIX_UTILS_EXPORT long celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); +CELIX_UTILS_EXPORT long +celix_properties_getAsLong(const celix_properties_t* properties, const char* key, long defaultValue); /** * @brief Set the value of a property to a long integer. @@ -283,7 +292,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setLong(celix_properties_t* p * boolean, or if the value cannot be converted to a boolean. If the value is a string, it will be converted * to a boolean if possible. */ -CELIX_UTILS_EXPORT bool celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); +CELIX_UTILS_EXPORT bool +celix_properties_getAsBool(const celix_properties_t* properties, const char* key, bool defaultValue); /** * @brief Set the value of a property to a boolean. @@ -303,7 +313,9 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setBool(celix_properties_t* p * @param[in] val The double value to set the property to. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_setDouble(celix_properties_t* properties, const char* key, double val); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setDouble(celix_properties_t* properties, + const char* key, + double val); /** * @brief Get the value of a property as a double. @@ -316,7 +328,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setDouble(celix_properties_t* * a double, or if the value cannot be converted to a double. If the value is a string, it will be converted * to a double if possible. */ -CELIX_UTILS_EXPORT double celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); +CELIX_UTILS_EXPORT double +celix_properties_getAsDouble(const celix_properties_t* properties, const char* key, double defaultValue); /** * @brief Set the value of a property as a Celix version. @@ -328,7 +341,9 @@ CELIX_UTILS_EXPORT double celix_properties_getAsDouble(const celix_properties_t* * @param[in] version The value to set. The function will make a copy of this object and store it in the property set. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version); +CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t* properties, + const char* key, + const celix_version_t* version); /** * @brief Set the value of a property as a Celix version. @@ -341,8 +356,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t * @param[in] version The value to set. The function will store a reference to this object in the property set and * takes ownership of the provided version. */ -CELIX_UTILS_EXPORT void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); - +CELIX_UTILS_EXPORT void +celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); /** * @brief Get the Celix version value of a property. @@ -355,10 +370,8 @@ CELIX_UTILS_EXPORT void celix_properties_setVersionWithoutCopy(celix_properties_ * @return The value of the property if it is a Celix version, or the default value if the property is not set or the * value is not a Celix version. */ -CELIX_UTILS_EXPORT const celix_version_t* celix_properties_getVersion( - const celix_properties_t* properties, - const char* key, - const celix_version_t* defaultValue); +CELIX_UTILS_EXPORT const celix_version_t* +celix_properties_getVersion(const celix_properties_t* properties, const char* key, const celix_version_t* defaultValue); /** * @brief Get the value of a property as a Celix version. @@ -377,10 +390,9 @@ CELIX_UTILS_EXPORT const celix_version_t* celix_properties_getVersion( * is not a valid Celix version. * @retval NULL if version cannot be found/converted and the defaultValue is NULL. */ -CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion( - const celix_properties_t* properties, - const char* key, - const celix_version_t* defaultValue); +CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion(const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue); /** * @brief Get the number of properties in a property set. @@ -433,7 +445,8 @@ CELIX_UTILS_EXPORT bool celix_propertiesIterator_isEnd(const celix_properties_it * @param[in] b The second iterator to compare. * @return true if the iterators are equal, false otherwise. */ -CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const celix_properties_iterator_t* b); +CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, + const celix_properties_iterator_t* b); /** * @brief Iterate over the entries in the specified celix_properties_t object. @@ -454,9 +467,10 @@ CELIX_UTILS_EXPORT bool celix_propertiesIterator_equals(const celix_properties_i * } * @endcode */ -#define CELIX_PROPERTIES_ITERATE(props, iterName) \ - for (celix_properties_iterator_t iterName = celix_properties_begin((props)); \ - !celix_propertiesIterator_isEnd(&(iterName)); celix_propertiesIterator_next(&(iterName))) +#define CELIX_PROPERTIES_ITERATE(props, iterName) \ + for (celix_properties_iterator_t iterName = celix_properties_begin((props)); \ + !celix_propertiesIterator_isEnd(&(iterName)); \ + celix_propertiesIterator_next(&(iterName))) #ifdef __cplusplus } diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index f59d2fe32..f7b8bb516 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -367,7 +367,6 @@ static void celix_hashMap_clear(celix_hash_map_t* map) { while (entry != NULL) { celix_hash_map_entry_t* removedEntry = entry; entry = entry->next; - \ char* removedKey = NULL; if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { removedKey = (char*)removedEntry->key.strKey; @@ -426,17 +425,18 @@ celix_string_hash_map_t* celix_stringHashMap_createWithOptions(const celix_strin unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); + if (status != CELIX_SUCCESS) { + celix_err_push("Cannot initialize hash map"); + return NULL; + } + map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; map->genericMap.removedCallbackData = opts->removedCallbackData; map->genericMap.removedStringEntryCallback = opts->removedCallback; map->genericMap.removedStringKeyCallback = opts->removedKeyCallback; map->genericMap.storeKeysWeakly = opts->storeKeysWeakly; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); - if (status != CELIX_SUCCESS) { - celix_err_push("Cannot initialize hash map"); - return NULL; - } return celix_steal_ptr(map); } @@ -447,22 +447,25 @@ celix_string_hash_map_t* celix_stringHashMap_create() { celix_long_hash_map_t* celix_longHashMap_createWithOptions(const celix_long_hash_map_create_options_t* opts) { celix_autofree celix_long_hash_map_t* map = calloc(1, sizeof(*map)); + if (!map) { celix_err_push("Cannot allocate memory for hash map"); return NULL; } + unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; - map->genericMap.removedCallbackData = opts->removedCallbackData; - map->genericMap.removedLongEntryCallback = opts->removedCallback; - map->genericMap.storeKeysWeakly = false; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_equals); if (status != CELIX_SUCCESS) { celix_err_push("Cannot initialize hash map"); return NULL; } + + map->genericMap.simpleRemovedCallback = opts->simpleRemovedCallback; + map->genericMap.removedCallbackData = opts->removedCallbackData; + map->genericMap.removedLongEntryCallback = opts->removedCallback; + map->genericMap.storeKeysWeakly = false; + return celix_steal_ptr(map); } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 97a9a87ca..870d7cbae 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -20,19 +20,19 @@ #include "properties.h" #include "celix_properties.h" -#include -#include -#include -#include -#include #include +#include #include +#include +#include +#include +#include -#include "celix_err.h" #include "celix_build_assert.h" -#include "celix_utils.h" +#include "celix_err.h" #include "celix_string_hash_map.h" - +#include "celix_utils.h" +#include "celix_stdlib_cleanup.h" #define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 512 #define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16 @@ -73,30 +73,19 @@ struct celix_properties { int currentEntriesBufferIndex; }; -#define MALLOC_BLOCK_SIZE 5 +#define MALLOC_BLOCK_SIZE 5 -static void celix_properties_parseLine(const char* line, celix_properties_t *props); +static void celix_properties_parseLine(const char* line, celix_properties_t* props); -properties_pt properties_create(void) { - return celix_properties_create(); -} +properties_pt properties_create(void) { return celix_properties_create(); } -void properties_destroy(properties_pt properties) { - celix_properties_destroy(properties); -} +void properties_destroy(properties_pt properties) { celix_properties_destroy(properties); } -properties_pt properties_load(const char* filename) { - return celix_properties_load(filename); -} +properties_pt properties_load(const char* filename) { return celix_properties_load(filename); } -properties_pt properties_loadWithStream(FILE *file) { - return celix_properties_loadWithStream(file); -} - -properties_pt properties_loadFromString(const char *input){ - return celix_properties_loadFromString(input); -} +properties_pt properties_loadWithStream(FILE* file) { return celix_properties_loadWithStream(file); } +properties_pt properties_loadFromString(const char* input) { return celix_properties_loadFromString(input); } /** * Header is ignored for now, cannot handle comments yet @@ -105,8 +94,8 @@ void properties_store(properties_pt properties, const char* filename, const char celix_properties_store(properties, filename, header); } -celix_status_t properties_copy(properties_pt properties, properties_pt *out) { - celix_properties_t *copy = celix_properties_copy(properties); +celix_status_t properties_copy(properties_pt properties, properties_pt* out) { + celix_properties_t* copy = celix_properties_copy(properties); *out = copy; return copy == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS; } @@ -123,19 +112,16 @@ void properties_set(properties_pt properties, const char* key, const char* value celix_properties_set(properties, key, value); } -void properties_unset(properties_pt properties, const char* key) { - celix_properties_unset(properties, key); -} +void properties_unset(properties_pt properties, const char* key) { celix_properties_unset(properties, key); } -static void updateBuffers(char **key, char ** value, char **output, int outputPos, int *key_len, int *value_len) { +static void updateBuffers(char** key, char** value, char** output, int outputPos, int* key_len, int* value_len) { if (*output == *key) { if (outputPos == (*key_len) - 1) { (*key_len) += MALLOC_BLOCK_SIZE; *key = realloc(*key, *key_len); *output = *key; } - } - else { + } else { if (outputPos == (*value_len) - 1) { (*value_len) += MALLOC_BLOCK_SIZE; *value = realloc(*value, *value_len); @@ -169,13 +155,12 @@ static char* celix_properties_createString(celix_properties_t* properties, const * optimization. */ static void celix_properties_freeString(celix_properties_t* properties, char* str) { - if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || - str == CELIX_PROPERTIES_BOOL_FALSE_STRVAL || - str == CELIX_PROPERTIES_EMPTY_STRVAL) { - //str is static const char* const -> nop + if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == CELIX_PROPERTIES_BOOL_FALSE_STRVAL || + str == CELIX_PROPERTIES_EMPTY_STRVAL) { + // str is static const char* const -> nop } else if (str >= properties->stringBuffer && - str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { - //str is part of the properties string buffer -> nop + str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { + // str is part of the properties string buffer -> nop } else { free(str); } @@ -184,14 +169,13 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st /** * Fill entry and optional use the short properties optimization string buffer. */ -static celix_status_t celix_properties_fillEntry( - celix_properties_t *properties, - celix_properties_entry_t* entry, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { +static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, + celix_properties_entry_t* entry, + const char** strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + celix_version_t* versionValue) { char convertedValueBuffer[32]; if (strValue != NULL) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; @@ -257,8 +241,8 @@ static celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* * Create entry and optionally use the short properties optimization entries buffer and take ownership of the * provided key and value strings. */ -static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_properties_t *properties, - const char *strValue) { +static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_properties_t* properties, + const char* strValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { return NULL; @@ -269,29 +253,27 @@ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_pr return entry; } - /** * Create entry and optionally use the short properties optimization buffers. * Only 1 of the types values (strValue, LongValue, etc) should be provided. */ -static celix_properties_entry_t* celix_properties_createEntry( - celix_properties_t *properties, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { +static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t* properties, + const char** strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + celix_version_t* versionValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { return NULL; } - celix_status_t status = celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, - boolValue, versionValue); + celix_status_t status = + celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); if (status != CELIX_SUCCESS) { if (entry >= properties->entriesBuffer && entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { - //entry is part of the properties entries buffer -> nop. + // entry is part of the properties entries buffer -> nop. } else { free(entry); } @@ -303,11 +285,12 @@ static celix_properties_entry_t* celix_properties_createEntry( static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) { celix_properties_freeString(properties, (char*)entry->value); if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - celix_version_destroy(entry->typed.versionValue); + celix_version_destroy((celix_version_t*)entry->typed.versionValue); } + if (entry >= properties->entriesBuffer && entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { - //entry is part of the properties entries buffer -> nop. + // entry is part of the properties entries buffer -> nop. } else { free(entry); } @@ -317,31 +300,30 @@ static void celix_properties_destroyEntry(celix_properties_t* properties, celix_ * Create and add entry and optionally use the short properties optimization buffers. * Only 1 of the types values (strValue, LongValue, etc) should be provided. */ -static celix_status_t celix_properties_createAndSetEntry( - celix_properties_t *properties, - const char* key, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { +static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* properties, + const char* key, + const char** strValue, + const long* longValue, + const double* doubleValue, + const bool* boolValue, + celix_version_t* versionValue) { if (!properties) { - return CELIX_SUCCESS; //silently ignore + return CELIX_SUCCESS; // silently ignore } if (!key) { celix_err_push("Cannot set property with NULL key"); - return CELIX_SUCCESS; //print error and ignore + return CELIX_SUCCESS; // print error and ignore } - celix_properties_entry_t* entry = celix_properties_createEntry(properties, strValue, longValue, doubleValue, - boolValue, versionValue); + celix_properties_entry_t* entry = + celix_properties_createEntry(properties, strValue, longValue, doubleValue, boolValue, versionValue); if (!entry) { return CELIX_ENOMEM; } const char* mapKey = key; if (!celix_stringHashMap_hasKey(properties->map, key)) { - //new entry, needs new allocated key; + // new entry, needs new allocated key; mapKey = celix_properties_createString(properties, key); if (!mapKey) { celix_properties_destroyEntry(properties, entry); @@ -364,13 +346,15 @@ static void celix_properties_removeKeyCallback(void* handle, char* key) { celix_properties_freeString(properties, key); } -static void celix_properties_removeEntryCallback(void* handle, const char* key __attribute__((unused)), celix_hash_map_value_t val) { +static void celix_properties_removeEntryCallback(void* handle, + const char* key __attribute__((unused)), + celix_hash_map_value_t val) { celix_properties_t* properties = handle; celix_properties_entry_t* entry = val.ptrValue; celix_properties_destroyEntry(properties, entry); } -celix_properties_t* celix_properties_create(void) { +celix_properties_t* celix_properties_create() { celix_properties_t* props = malloc(sizeof(*props)); if (props != NULL) { celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; @@ -390,30 +374,30 @@ celix_properties_t* celix_properties_create(void) { return props; } -void celix_properties_destroy(celix_properties_t *props) { +void celix_properties_destroy(celix_properties_t* props) { if (props != NULL) { celix_stringHashMap_destroy(props->map); free(props); } } -celix_properties_t* celix_properties_load(const char *filename) { - FILE *file = fopen(filename, "r"); +celix_properties_t* celix_properties_load(const char* filename) { + FILE* file = fopen(filename, "r"); if (file == NULL) { return NULL; } - celix_properties_t *props = celix_properties_loadWithStream(file); + celix_properties_t* props = celix_properties_loadWithStream(file); fclose(file); return props; } -static void celix_properties_parseLine(const char* line, celix_properties_t *props) { +static void celix_properties_parseLine(const char* line, celix_properties_t* props) { int linePos; int outputPos; bool precedingCharIsBackslash = false; bool isComment = false; - char *output = NULL; + char* output = NULL; int key_len = MALLOC_BLOCK_SIZE; int value_len = MALLOC_BLOCK_SIZE; linePos = 0; @@ -422,53 +406,48 @@ static void celix_properties_parseLine(const char* line, celix_properties_t *pro output = NULL; outputPos = 0; - //Ignore empty lines + // Ignore empty lines if (line[0] == '\n' && line[1] == '\0') { return; } - char *key = calloc(1, key_len); - char *value = calloc(1, value_len); + char* key = calloc(1, key_len); + char* value = calloc(1, value_len); key[0] = '\0'; value[0] = '\0'; while (line[linePos] != '\0') { if (line[linePos] == ' ' || line[linePos] == '\t') { if (output == NULL) { - //ignore + // ignore linePos += 1; continue; } - } - else { + } else { if (output == NULL) { output = key; } } if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') { if (precedingCharIsBackslash) { - //escaped special character + // escaped special character output[outputPos++] = line[linePos]; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); precedingCharIsBackslash = false; - } - else { + } else { if (line[linePos] == '#' || line[linePos] == '!') { if (outputPos == 0) { isComment = true; break; - } - else { + } else { output[outputPos++] = line[linePos]; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); } - } - else { // = or : - if (output == value) { //already have a seperator + } else { // = or : + if (output == value) { // already have a seperator output[outputPos++] = line[linePos]; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); - } - else { + } else { output[outputPos++] = '\0'; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); output = value; @@ -476,15 +455,13 @@ static void celix_properties_parseLine(const char* line, celix_properties_t *pro } } } - } - else if (line[linePos] == '\\') { - if (precedingCharIsBackslash) { //double backslash -> backslash + } else if (line[linePos] == '\\') { + if (precedingCharIsBackslash) { // double backslash -> backslash output[outputPos++] = '\\'; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); } precedingCharIsBackslash = true; - } - else { //normal character + } else { // normal character precedingCharIsBackslash = false; output[outputPos++] = line[linePos]; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); @@ -496,21 +473,21 @@ static void celix_properties_parseLine(const char* line, celix_properties_t *pro } if (!isComment) { - //printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); + // printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); celix_properties_set(props, celix_utils_trimInPlace(key), celix_utils_trimInPlace(value)); } - if(key) { + if (key) { free(key); free(value); } } -celix_properties_t* celix_properties_loadWithStream(FILE *file) { +celix_properties_t* celix_properties_loadWithStream(FILE* file) { if (file == NULL) { return NULL; } - celix_properties_t *props = celix_properties_create(); + celix_properties_t* props = celix_properties_create(); if (props == NULL) { return NULL; } @@ -530,9 +507,9 @@ celix_properties_t* celix_properties_loadWithStream(FILE *file) { size_t rs = fread(fileBuffer, sizeof(char), fileSize, file); if (rs < fileSize) { - fprintf(stderr,"fread read only %zu bytes out of %zu\n", rs, fileSize); + fprintf(stderr, "fread read only %zu bytes out of %zu\n", rs, fileSize); } - fileBuffer[fileSize]='\0'; //ensure a '\0' at the end of the fileBuffer + fileBuffer[fileSize] = '\0'; // ensure a '\0' at the end of the fileBuffer char* savePtr = NULL; char* line = strtok_r(fileBuffer, "\n", &savePtr); @@ -545,28 +522,28 @@ celix_properties_t* celix_properties_loadWithStream(FILE *file) { return props; } -celix_properties_t* celix_properties_loadFromString(const char *input) { - celix_properties_t *props = celix_properties_create(); +celix_properties_t* celix_properties_loadFromString(const char* input) { + celix_properties_t* props = celix_properties_create(); - char *in = strdup(input); - char *line = NULL; - char *saveLinePointer = NULL; + char* in = strdup(input); + char* line = NULL; + char* saveLinePointer = NULL; bool firstTime = true; do { - if (firstTime){ + if (firstTime) { line = strtok_r(in, "\n", &saveLinePointer); firstTime = false; - }else { + } else { line = strtok_r(NULL, "\n", &saveLinePointer); } - if (line == NULL){ + if (line == NULL) { break; } celix_properties_parseLine(line, props); - } while(line != NULL); + } while (line != NULL); free(in); @@ -590,8 +567,8 @@ static int celix_properties_storeEscapedString(FILE* file, const char* str) { return rc; } -celix_status_t celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { - FILE *file = fopen (filename, "w+" ); +celix_status_t celix_properties_store(celix_properties_t* properties, const char* filename, const char* header) { + FILE* file = fopen(filename, "w+"); if (file == NULL) { return CELIX_FILE_IO_EXCEPTION; @@ -632,8 +609,8 @@ celix_status_t celix_properties_store(celix_properties_t *properties, const char return rc != EOF ? CELIX_SUCCESS : CELIX_FILE_IO_EXCEPTION; } -celix_properties_t* celix_properties_copy(const celix_properties_t *properties) { - celix_properties_t *copy = celix_properties_create(); +celix_properties_t* celix_properties_copy(const celix_properties_t* properties) { + celix_properties_t* copy = celix_properties_create(); if (properties == NULL) { return copy; } @@ -660,10 +637,10 @@ celix_properties_value_type_e celix_properties_getType(const celix_properties_t* return entry == NULL ? CELIX_PROPERTIES_VALUE_TYPE_UNSET : entry->valueType; } -const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { +const char* celix_properties_get(const celix_properties_t* properties, const char* key, const char* defaultValue) { celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); if (entry != NULL) { - return entry->value; + return entry->value; } return defaultValue; } @@ -676,53 +653,61 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr return entry; } -celix_status_t celix_properties_set(celix_properties_t *properties, const char *key, const char *value) { +celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char* value) { return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL); } -void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value) { +celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) { if (properties != NULL && key != NULL && value != NULL) { celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value); - if (entry != NULL) { - bool replaced = celix_stringHashMap_put(properties->map, key, entry); - if (replaced) { - free(key); - } + if (!entry) { + celix_err_push("Failed to create entry for property."); + free(key); + return CELIX_ENOMEM; + } + + bool alreadyExist = celix_stringHashMap_hasKey(properties->map, key); + celix_status_t status = celix_stringHashMap_put(properties->map, key, entry); + if (status == CELIX_SUCCESS && alreadyExist) { + free(key); } + return status; } + return CELIX_SUCCESS; // silently ignore NULL properties, key or value } -celix_status_t celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry) { +celix_status_t +celix_properties_setEntry(celix_properties_t* properties, const char* key, const celix_properties_entry_t* entry) { if (entry) { switch (entry->valueType) { - case CELIX_PROPERTIES_VALUE_TYPE_LONG: - return celix_properties_setLong(properties, key, entry->typed.longValue); - case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: - return celix_properties_setDouble(properties, key, entry->typed.doubleValue); - case CELIX_PROPERTIES_VALUE_TYPE_BOOL: - return celix_properties_setBool(properties, key, entry->typed.boolValue); - case CELIX_PROPERTIES_VALUE_TYPE_VERSION: - return celix_properties_setVersion(properties, key, entry->typed.versionValue); - default: //STRING - return celix_properties_set(properties, key, entry->value); + case CELIX_PROPERTIES_VALUE_TYPE_LONG: + return celix_properties_setLong(properties, key, entry->typed.longValue); + case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: + return celix_properties_setDouble(properties, key, entry->typed.doubleValue); + case CELIX_PROPERTIES_VALUE_TYPE_BOOL: + return celix_properties_setBool(properties, key, entry->typed.boolValue); + case CELIX_PROPERTIES_VALUE_TYPE_VERSION: + return celix_properties_setVersion(properties, key, entry->typed.versionValue); + default: // STRING + return celix_properties_set(properties, key, entry->value); } } - return CELIX_SUCCESS; //silently ignore NULL entry + return CELIX_SUCCESS; // silently ignore NULL entry } -void celix_properties_unset(celix_properties_t *properties, const char *key) { +void celix_properties_unset(celix_properties_t* properties, const char* key) { if (properties != NULL) { celix_stringHashMap_remove(properties->map, key); } } -long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue) { +long celix_properties_getAsLong(const celix_properties_t* props, const char* key, long defaultValue) { long result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { return entry->typed.longValue; } else if (entry != NULL) { - char *enptr = NULL; + char* enptr = NULL; errno = 0; long r = strtol(entry->value, &enptr, 10); if (enptr != entry->value && errno == 0) { @@ -732,17 +717,17 @@ long celix_properties_getAsLong(const celix_properties_t *props, const char *key return result; } -celix_status_t celix_properties_setLong(celix_properties_t *props, const char *key, long value) { +celix_status_t celix_properties_setLong(celix_properties_t* props, const char* key, long value) { return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL); } -double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue) { +double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) { double result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { return entry->typed.doubleValue; } else if (entry != NULL) { - char *enptr = NULL; + char* enptr = NULL; errno = 0; double r = strtod(entry->value, &enptr); if (enptr != entry->value && errno == 0) { @@ -752,11 +737,11 @@ double celix_properties_getAsDouble(const celix_properties_t *props, const char return result; } -celix_status_t celix_properties_setDouble(celix_properties_t *props, const char *key, double val) { +celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* key, double val) { return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL); } -bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue) { +bool celix_properties_getAsBool(const celix_properties_t* props, const char* key, bool defaultValue) { bool result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { @@ -764,7 +749,7 @@ bool celix_properties_getAsBool(const celix_properties_t *props, const char *key } else if (entry != NULL) { char buf[32]; snprintf(buf, 32, "%s", entry->value); - char *trimmed = celix_utils_trimInPlace(buf); + char* trimmed = celix_utils_trimInPlace(buf); if (strncasecmp("true", trimmed, strlen("true")) == 0) { result = true; } else if (strncasecmp("false", buf, strlen("false")) == 0) { @@ -774,14 +759,13 @@ bool celix_properties_getAsBool(const celix_properties_t *props, const char *key return result; } -celix_status_t celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { +celix_status_t celix_properties_setBool(celix_properties_t* props, const char* key, bool val) { return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); } -const celix_version_t* celix_properties_getVersion( - const celix_properties_t* properties, - const char* key, - const celix_version_t* defaultValue) { +const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue) { celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { return entry->typed.versionValue; @@ -789,10 +773,9 @@ const celix_version_t* celix_properties_getVersion( return defaultValue; } -celix_version_t* celix_properties_getAsVersion( - const celix_properties_t* properties, - const char* key, - const celix_version_t* defaultValue) { +celix_version_t* celix_properties_getAsVersion(const celix_properties_t* properties, + const char* key, + const celix_version_t* defaultValue) { celix_properties_entry_t* entry = celix_properties_getEntry(properties, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { return celix_version_copy(entry->typed.versionValue); @@ -806,7 +789,8 @@ celix_version_t* celix_properties_getAsVersion( return defaultValue == NULL ? NULL : celix_version_copy(defaultValue); } -celix_status_t celix_properties_setVersion(celix_properties_t *properties, const char *key, const celix_version_t* version) { +celix_status_t +celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version) { return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); } @@ -814,14 +798,14 @@ void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, cons celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version); } -int celix_properties_size(const celix_properties_t *properties) { +int celix_properties_size(const celix_properties_t* properties) { return (int)celix_stringHashMap_size(properties->map); } typedef struct { celix_string_hash_map_iterator_t mapIter; const celix_properties_t* props; -} celix_properties_iterator_internal_t; +} celix_properties_iterator_internal_t; celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties) { CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); @@ -856,7 +840,7 @@ celix_properties_iterator_t celix_properties_end(const celix_properties_t* prope return iter; } -void celix_propertiesIterator_next(celix_properties_iterator_t *iter) { +void celix_propertiesIterator_next(celix_properties_iterator_t* iter) { celix_properties_iterator_internal_t internalIter; memcpy(&internalIter, iter->_data, sizeof(internalIter)); celix_stringHashMapIterator_next(&internalIter.mapIter); From 287d6f07fcbb5142a08030f3da41d2ad4d462586 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 22 Oct 2023 20:55:04 +0200 Subject: [PATCH 29/63] Extend error injection test for celix properties --- libs/error_injector/stdio/CMakeLists.txt | 2 + libs/error_injector/stdio/include/stdio_ei.h | 4 + libs/error_injector/stdio/src/stdio_ei.cc | 17 +- .../src/PropertiesErrorInjectionTestSuite.cc | 188 ++++++++++++++---- libs/utils/gtest/src/PropertiesTestSuite.cc | 5 + libs/utils/include/celix_properties.h | 2 + libs/utils/src/celix_hash_map_private.h | 10 +- libs/utils/src/celix_properties_private.h | 43 ++++ libs/utils/src/properties.c | 38 ++-- 9 files changed, 256 insertions(+), 53 deletions(-) create mode 100644 libs/utils/src/celix_properties_private.h diff --git a/libs/error_injector/stdio/CMakeLists.txt b/libs/error_injector/stdio/CMakeLists.txt index 2068795ea..8a4675b2e 100644 --- a/libs/error_injector/stdio/CMakeLists.txt +++ b/libs/error_injector/stdio/CMakeLists.txt @@ -28,5 +28,7 @@ target_link_options(stdio_ei INTERFACE LINKER:--wrap,fseek LINKER:--wrap,ftell LINKER:--wrap,fread + LINKER:--wrap,fputc + LINKER:--wrap,fputs ) add_library(Celix::stdio_ei ALIAS stdio_ei) diff --git a/libs/error_injector/stdio/include/stdio_ei.h b/libs/error_injector/stdio/include/stdio_ei.h index a3018f816..210e546b9 100644 --- a/libs/error_injector/stdio/include/stdio_ei.h +++ b/libs/error_injector/stdio/include/stdio_ei.h @@ -40,6 +40,10 @@ CELIX_EI_DECLARE(ftell, long); CELIX_EI_DECLARE(fread, size_t); +CELIX_EI_DECLARE(fputc, int); + +CELIX_EI_DECLARE(fputs, int); + #ifdef __cplusplus } #endif diff --git a/libs/error_injector/stdio/src/stdio_ei.cc b/libs/error_injector/stdio/src/stdio_ei.cc index ca7107524..a3d3ee779 100644 --- a/libs/error_injector/stdio/src/stdio_ei.cc +++ b/libs/error_injector/stdio/src/stdio_ei.cc @@ -83,4 +83,19 @@ size_t __wrap_fread(void* __restrict __ptr, size_t __size, size_t __n, FILE* __r CELIX_EI_IMPL(fread); return __real_fread(__ptr, __size, __n, __s); } -} \ No newline at end of file + +int __real_fputc(int __c, FILE* __stream); +CELIX_EI_DEFINE(fputc, int) +int __wrap_fputc(int __c, FILE* __stream) { + CELIX_EI_IMPL(fputc); + return __real_fputc(__c, __stream); +} + +int __real_fputs(const char* __s, FILE* __stream); +CELIX_EI_DEFINE(fputs, int) +int __wrap_fputs(const char* __s, FILE* __stream) { + CELIX_EI_IMPL(fputs); + return __real_fputs(__s, __stream); +} + +} diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index d3ad87578..6bd78a116 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -20,77 +20,85 @@ #include +#include "celix/Properties.h" #include "celix_cleanup.h" +#include "celix_err.h" #include "celix_properties.h" -#include "celix/Properties.h" +#include "celix_properties_private.h" -#include "malloc_ei.h" #include "celix_string_hash_map_ei.h" #include "celix_utils_ei.h" +#include "malloc_ei.h" +#include "stdio_ei.h" class PropertiesErrorInjectionTestSuite : public ::testing::Test { -public: + public: PropertiesErrorInjectionTestSuite() = default; ~PropertiesErrorInjectionTestSuite() override { celix_ei_expect_malloc(nullptr, 0, nullptr); celix_ei_expect_celix_stringHashMap_create(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_fopen(nullptr, 0, nullptr); + celix_ei_expect_fputc(nullptr, 0, 0); + celix_ei_expect_fseek(nullptr, 0, 0); + } + + void fillOptimizationCache(celix_properties_t* props) { + for (int i = 0; i < 50; ++i) { + const char* val = "1234567890"; + char key[10]; + snprintf(key, sizeof(key), "key%i", i); + celix_properties_set(props, key, val); + } } }; TEST_F(PropertiesErrorInjectionTestSuite, CreateFailureTest) { - //C API - celix_ei_expect_malloc((void *)celix_properties_create, 0, nullptr); + // C API + celix_ei_expect_malloc((void*)celix_properties_create, 0, nullptr); ASSERT_EQ(nullptr, celix_properties_create()); - //C++ API - celix_ei_expect_malloc((void *)celix_properties_create, 0, nullptr); + // C++ API + celix_ei_expect_malloc((void*)celix_properties_create, 0, nullptr); ASSERT_THROW(celix::Properties(), std::bad_alloc); } TEST_F(PropertiesErrorInjectionTestSuite, CopyFailureTest) { - //C API + // C API celix_autoptr(celix_properties_t) prop = celix_properties_create(); ASSERT_NE(nullptr, prop); - celix_ei_expect_celix_stringHashMap_createWithOptions((void *)celix_properties_create, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)celix_properties_create, 0, nullptr); ASSERT_EQ(nullptr, celix_properties_copy(prop)); - //C++ API + // C++ API const celix::Properties cxxProp{}; - celix_ei_expect_celix_stringHashMap_createWithOptions((void *)celix_properties_create, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)celix_properties_create, 0, nullptr); ASSERT_THROW(celix::Properties{cxxProp}, std::bad_alloc); } -//TODO store, load, setWithoutCopy - TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { - //C API - //Given a celix properties object with a filled optimization cache + // C API + // Given a celix properties object with a filled optimization cache celix_autoptr(celix_properties_t) props = celix_properties_create(); - for (int i = 0; i < 50; ++i) { - const char* val = "1234567890"; - char key[10]; - snprintf(key, sizeof(key), "key%i", i); - celix_properties_set(props, key, val); - } + fillOptimizationCache(props); - //When a malloc error injection is set for celix_properties_set with level 3 (during alloc entry) - celix_ei_expect_malloc((void *)celix_properties_set, 3, nullptr); - //Then the celix_properties_set call fails + // When a malloc error injection is set for celix_properties_set (during alloc entry) + celix_ei_expect_malloc((void*)celix_properties_allocEntry, 0, nullptr); + // Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); - //When a celix_utils_strdup error injection is set for celix_properties_set with level 4 (during strdup key) - celix_ei_expect_celix_utils_strdup((void *)celix_properties_set, 4, nullptr); - //Then the celix_properties_set call fails + // When a celix_utils_strdup error injection is set for celix_properties_set (during strdup key) + celix_ei_expect_celix_utils_strdup((void*)celix_properties_createString, 0, nullptr); + // Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); - //When a celix_stringHashMap_put error injection is set for celix_properties_set with level 1 (during put) - celix_ei_expect_celix_stringHashMap_put((void *)celix_properties_set, 1, CELIX_ENOMEM); - //Then the celix_properties_set call fails + // When a celix_stringHashMap_put error injection is set for celix_properties_set with level 1 (during put) + celix_ei_expect_celix_stringHashMap_put((void*)celix_properties_set, 1, CELIX_ENOMEM); + // Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); - //C++ API - //Given a celix properties object with a filled optimization cache + // C++ API + // Given a celix properties object with a filled optimization cache celix::Properties cxxProps{}; for (int i = 0; i < 50; ++i) { const char* val = "1234567890"; @@ -99,8 +107,118 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { cxxProps.set(key, val); } - //When a malloc error injection is set for celix_properties_set with level 2 and ordinal 1 (during alloc entry) - celix_ei_expect_malloc((void *)celix_properties_set, 3, nullptr, 1); - //Then the Properties:set throws a bad_alloc exception + // When a malloc error injection is set for celix_properties_set (during alloc entry) + celix_ei_expect_malloc((void*)celix_properties_allocEntry, 0, nullptr); + // Then the Properties:set throws a bad_alloc exception ASSERT_THROW(cxxProps.set("key", "value"), std::bad_alloc); } + +TEST_F(PropertiesErrorInjectionTestSuite, StoreFailureTest) { + // C API + // Given a celix properties object + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_properties_set(props, "key", "value"); + + // When a fopen error injection is set for celix_properties_store (during fopen) + celix_ei_expect_fopen((void*)celix_properties_store, 0, nullptr); + // Then the celix_properties_store call fails + auto status = celix_properties_store(props, "file", nullptr); + ASSERT_EQ(status, CELIX_FILE_IO_EXCEPTION); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + // When a fputc error injection is set for celix_properties_store (during fputc) + celix_ei_expect_fputc((void*)celix_properties_store, 0, EOF); + // Then the celix_properties_store call fails + status = celix_properties_store(props, "file", nullptr); + ASSERT_EQ(status, CELIX_FILE_IO_EXCEPTION); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + // C++ API + // Given a C++ celix properties object. + auto cxxProps = celix::Properties{}; + cxxProps.set("key", "value"); + + // When a fopen error injection is set for celix_properties_store (during fopen) + celix_ei_expect_fopen((void*)celix_properties_store, 0, nullptr); + // Then the Properties:store throws a celix::IOException exception + EXPECT_THROW(cxxProps.store("file"), celix::IOException); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); +} + +TEST_F(PropertiesErrorInjectionTestSuite, LoadFailureTest) { + // C API + // Given a fmemstream buffer with a properties file + const char* content = "key=value\n"; + auto* memStream = fmemopen((void*)content, strlen(content), "r"); + + // When a fopen error injection is set for celix_properties_load (during fopen) + celix_ei_expect_fopen((void*)celix_properties_load, 0, nullptr); + // Then the celix_properties_load call fails + auto props = celix_properties_load("file"); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + // When a malloc error injection is set for celix_properties_loadWithStream (during properties create) + celix_ei_expect_malloc((void*)celix_properties_create, 0, nullptr); + // Then the celix_properties_loadWithStream call fails + props = celix_properties_loadWithStream(memStream); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + // When a fseek error injection is set for celix_properties_loadWithStream + celix_ei_expect_fseek((void*)celix_properties_loadWithStream, 0, -1); + // Then the celix_properties_loadWithStream call fails + props = celix_properties_loadWithStream(memStream); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + + // When a malloc error injection is set for celix_properties_loadWithStream + celix_ei_expect_malloc((void*)celix_properties_loadWithStream, 0, nullptr); + // Then the celix_properties_loadWithStream call fails + props = celix_properties_loadWithStream(memStream); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + //C++ API + // When a fopen error injection is set for celix_properties_load (during fopen) + celix_ei_expect_fopen((void*)celix_properties_load, 0, nullptr); + // Then the celix::Properties::load call throws a celix::IOException exception + EXPECT_THROW(celix::Properties::load("file"), celix::IOException); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + fclose(memStream); +} + +TEST_F(PropertiesErrorInjectionTestSuite, SetWithoutCopyFailureTest) { + //Given a filled properties and a key and value + celix_autoptr(celix_properties_t) props = celix_properties_create(); + fillOptimizationCache(props); + char* key = celix_utils_strdup("key"); + char* val = celix_utils_strdup("value"); + + // When a malloc error injection is set for celix_properties_setWithoutCopy (during alloc entry) + celix_ei_expect_malloc((void*)celix_properties_allocEntry, 0, nullptr); + // Then the celix_properties_setWithoutCopy call fails + auto status = celix_properties_setWithoutCopy(props, key, val); + ASSERT_EQ(status, CELIX_ENOMEM); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 91844c17d..0007a32bf 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -613,3 +613,8 @@ TEST_F(PropertiesTestSuite, DeprecatedApiTest) { TEST_F(PropertiesTestSuite, PropertiesAutoCleanupTest) { celix_autoptr(celix_properties_t) props = celix_properties_create(); } + +TEST_F(PropertiesTestSuite, NullArgumentsTest) { + auto props = celix_properties_loadWithStream(nullptr); + EXPECT_EQ(nullptr, props); +} diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 144cd7b1b..e6405372e 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -224,6 +224,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* prope * modified after calling this function. * @param[in] value The value to set the property to. This string will be used directly, so it must not be freed or * modified after calling this function. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * When CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, diff --git a/libs/utils/src/celix_hash_map_private.h b/libs/utils/src/celix_hash_map_private.h index 9ad261f5c..a3c4d7d4f 100644 --- a/libs/utils/src/celix_hash_map_private.h +++ b/libs/utils/src/celix_hash_map_private.h @@ -16,6 +16,12 @@ *specific language governing permissions and limitations *under the License. */ + +/** + * @file celix_hash_map_private.h + * @brief Private header for celix_hash_map, with function used for whitebox testing. + */ + #ifndef CELIX_CELIX_HASH_MAP_PRIVATE_H #define CELIX_CELIX_HASH_MAP_PRIVATE_H @@ -26,10 +32,6 @@ extern "C" { #endif -/** - * @file Private header for celix_hash_map, with function used for whitebox testing. - */ - typedef struct celix_hash_map celix_hash_map_t; // opaque typedef struct celix_hash_map_entry celix_hash_map_entry_t; // opaque typedef union celix_hash_map_key celix_hash_map_key_t; // opaque diff --git a/libs/utils/src/celix_properties_private.h b/libs/utils/src/celix_properties_private.h new file mode 100644 index 000000000..5df0ea644 --- /dev/null +++ b/libs/utils/src/celix_properties_private.h @@ -0,0 +1,43 @@ +/* +* 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. +*/ + +/** + * @file celix_properties_private.h + * @brief Private Header file for the Celix Properties used for whitebox testing. + */ + +#ifndef CELIX_CELIX_PROPERTIES_PRIVATE_H +#define CELIX_CELIX_PROPERTIES_PRIVATE_H + +#include "celix_properties.h" + +#ifdef __cplusplus +extern "C" { +#endif + +celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties); + +char* celix_properties_createString(celix_properties_t* properties, const char* str); + + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_PROPERTIES_PRIVATE_H \ No newline at end of file diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 870d7cbae..6d38d750d 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -19,6 +19,7 @@ #include "properties.h" #include "celix_properties.h" +#include "celix_properties_private.h" #include #include @@ -134,7 +135,7 @@ static void updateBuffers(char** key, char** value, char** output, int outputPos * Create a new string from the provided str by either using strup or storing the string the short properties * optimization string buffer. */ -static char* celix_properties_createString(celix_properties_t* properties, const char* str) { +char* celix_properties_createString(celix_properties_t* properties, const char* str) { if (str == NULL) { return (char*)CELIX_PROPERTIES_EMPTY_STRVAL; } @@ -227,7 +228,7 @@ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, /** * Allocate entry and optionally use the short properties optimization entries buffer. */ -static celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties) { +celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties) { celix_properties_entry_t* entry; if (properties->currentEntriesBufferIndex < CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) { entry = &properties->entriesBuffer[properties->currentEntriesBufferIndex++]; @@ -384,6 +385,7 @@ void celix_properties_destroy(celix_properties_t* props) { celix_properties_t* celix_properties_load(const char* filename) { FILE* file = fopen(filename, "r"); if (file == NULL) { + celix_err_pushf("Cannot open file '%s'", filename); return NULL; } celix_properties_t* props = celix_properties_loadWithStream(file); @@ -487,21 +489,23 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { return NULL; } - celix_properties_t* props = celix_properties_create(); - if (props == NULL) { + + celix_autoptr(celix_properties_t) props = celix_properties_create(); + if (!props) { + celix_err_push("Failed to create properties"); return NULL; } - fseek(file, 0, SEEK_END); - ssize_t fileSize = ftell(file); - fseek(file, 0, SEEK_SET); - if (fileSize == 0) { - return props; + int rc = fseek(file, 0, SEEK_END); + if (rc != 0) { + celix_err_pushf("Cannot seek to end of file. Got error %i", errno); + return NULL; } + size_t fileSize = ftell(file); char* fileBuffer = malloc(fileSize + 1); if (fileBuffer == NULL) { - celix_properties_destroy(props); + celix_err_pushf("Cannot allocate memory for file buffer. Got error %i", errno); return NULL; } @@ -519,7 +523,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { } free(fileBuffer); - return props; + return celix_steal_ptr(props); } celix_properties_t* celix_properties_loadFromString(const char* input) { @@ -571,6 +575,7 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char FILE* file = fopen(filename, "w+"); if (file == NULL) { + celix_err_pushf("Cannot open file '%s'", filename); return CELIX_FILE_IO_EXCEPTION; } @@ -584,7 +589,7 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char rc = fputs(header, file); } if (rc != 0) { - rc = fputs("\n", file); + rc = fputc('\n', file); } } @@ -605,8 +610,14 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char } if (rc != EOF) { rc = fclose(file); + } else { + fclose(file); + } + if (rc == EOF) { + celix_err_push("Failed to write properties to file"); + return CELIX_FILE_IO_EXCEPTION; } - return rc != EOF ? CELIX_SUCCESS : CELIX_FILE_IO_EXCEPTION; + return CELIX_SUCCESS; } celix_properties_t* celix_properties_copy(const celix_properties_t* properties) { @@ -663,6 +674,7 @@ celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, c if (!entry) { celix_err_push("Failed to create entry for property."); free(key); + free(value); return CELIX_ENOMEM; } From 770818d4cc8f79bf87117da2a7eac28e6e75f14b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 23 Oct 2023 19:12:08 +0200 Subject: [PATCH 30/63] Add error injection for celix_properties_loadFromString --- .../src/PropertiesErrorInjectionTestSuite.cc | 21 +++++++++++ libs/utils/src/celix_properties_private.h | 6 +++ libs/utils/src/properties.c | 37 ++++++++----------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 6bd78a116..0b6f16439 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -30,6 +30,7 @@ #include "celix_utils_ei.h" #include "malloc_ei.h" #include "stdio_ei.h" +#include "celix_utils_ei.h" class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: @@ -41,6 +42,7 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { celix_ei_expect_fopen(nullptr, 0, nullptr); celix_ei_expect_fputc(nullptr, 0, 0); celix_ei_expect_fseek(nullptr, 0, 0); + celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } void fillOptimizationCache(celix_properties_t* props) { @@ -184,6 +186,14 @@ TEST_F(PropertiesErrorInjectionTestSuite, LoadFailureTest) { ASSERT_EQ(1, celix_err_getErrorCount()); celix_err_resetErrors(); + // When a fseek error injection is set for celix_properties_loadWithStream, ordinal 2 + celix_ei_expect_fseek((void*)celix_properties_loadWithStream, 0, -1, 2); + // Then the celix_properties_loadWithStream call fails + props = celix_properties_loadWithStream(memStream); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); // When a malloc error injection is set for celix_properties_loadWithStream celix_ei_expect_malloc((void*)celix_properties_loadWithStream, 0, nullptr); @@ -222,3 +232,14 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetWithoutCopyFailureTest) { ASSERT_EQ(1, celix_err_getErrorCount()); celix_err_resetErrors(); } + +TEST_F(PropertiesErrorInjectionTestSuite, LoadFromStringFailureTest) { + // When a strdup error injection is set for celix_properties_loadFromString (during strdup) + celix_ei_expect_celix_utils_strdup((void*)celix_properties_loadFromString, 0, nullptr); + // Then the celix_properties_loadFromString call fails + auto props = celix_properties_loadFromString("key=value"); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); +} diff --git a/libs/utils/src/celix_properties_private.h b/libs/utils/src/celix_properties_private.h index 5df0ea644..209b2ef0c 100644 --- a/libs/utils/src/celix_properties_private.h +++ b/libs/utils/src/celix_properties_private.h @@ -31,8 +31,14 @@ extern "C" { #endif +/** + * @brief Alloc new entry for the provided properties. Possible using the properties optimizer cache. + */ celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties); +/** + * @brief Create a new string for the provided properties. Possible using the properties optimizer cache. + */ char* celix_properties_createString(celix_properties_t* properties, const char* str); diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 6d38d750d..3fb8f04d9 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -502,6 +502,11 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { return NULL; } size_t fileSize = ftell(file); + rc = fseek(file, 0, SEEK_SET); + if (rc != 0) { + celix_err_pushf("Cannot seek to start of file. Got error %i", errno); + return NULL; + } char* fileBuffer = malloc(fileSize + 1); if (fileBuffer == NULL) { @@ -527,31 +532,21 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { } celix_properties_t* celix_properties_loadFromString(const char* input) { - celix_properties_t* props = celix_properties_create(); + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_autofree char* in = celix_utils_strdup(input); + if (!props || !in) { + celix_err_push("Failed to create properties or duplicate input string"); + return NULL; + } - char* in = strdup(input); char* line = NULL; char* saveLinePointer = NULL; - - bool firstTime = true; - do { - if (firstTime) { - line = strtok_r(in, "\n", &saveLinePointer); - firstTime = false; - } else { - line = strtok_r(NULL, "\n", &saveLinePointer); - } - - if (line == NULL) { - break; - } - + line = strtok_r(in, "\n", &saveLinePointer); + while (line != NULL) { celix_properties_parseLine(line, props); - } while (line != NULL); - - free(in); - - return props; + line = strtok_r(NULL, "\n", &saveLinePointer); + } + return celix_steal_ptr(props); } /** From 9979697b365127eec90aad06c7a202bbbafc3494 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 23 Oct 2023 20:25:47 +0200 Subject: [PATCH 31/63] Add equals for celix string/long hashmap en celix properties --- libs/utils/gtest/src/HashMapTestSuite.cc | 164 +++++++++++--------- libs/utils/gtest/src/PropertiesTestSuite.cc | 38 ++++- libs/utils/include/celix_long_hash_map.h | 12 ++ libs/utils/include/celix_properties.h | 12 ++ libs/utils/include/celix_string_hash_map.h | 13 ++ libs/utils/src/celix_hash_map.c | 39 ++++- libs/utils/src/properties.c | 73 +++++---- 7 files changed, 247 insertions(+), 104 deletions(-) diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index c6f3cfb8c..fed19b5e3 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -19,14 +19,14 @@ #include -#include "celix_utils.h" -#include "celix_string_hash_map.h" #include "celix_long_hash_map.h" -#include +#include "celix_string_hash_map.h" +#include "celix_utils.h" #include +#include class HashMapTestSuite : public ::testing::Test { -public: + public: /** * Create and fill string hash map with nrEntries entries. */ @@ -46,7 +46,7 @@ class HashMapTestSuite : public ::testing::Test { std::string key = "key" + std::to_string(i); celix_stringHashMap_putLong(map, key.c_str(), i); EXPECT_EQ(i, celix_stringHashMap_getLong(map, key.c_str(), 0)); - EXPECT_EQ(celix_stringHashMap_size(map), i+1); + EXPECT_EQ(celix_stringHashMap_size(map), i + 1); } } @@ -55,7 +55,7 @@ class HashMapTestSuite : public ::testing::Test { * This assumes that the map is filled using fillStringHashMap. */ void testGetEntriesFromStringMap(celix_string_hash_map_t* map, int nrOfEntriesToTest) { - std::uniform_int_distribution keyDistribution{0, (long)celix_stringHashMap_size(map)-1}; + std::uniform_int_distribution keyDistribution{0, (long)celix_stringHashMap_size(map) - 1}; for (int i = 0; i < nrOfEntriesToTest; ++i) { long rand = keyDistribution(generator); auto key = std::string{"key"} + std::to_string(rand); @@ -66,7 +66,7 @@ class HashMapTestSuite : public ::testing::Test { /** * Create and fill long hash map with nrEntries entries. */ - celix_long_hash_map_t * createLongHashMap(int nrEntries) { + celix_long_hash_map_t* createLongHashMap(int nrEntries) { auto* map = celix_longHashMap_create(); fillLongHashMap(map, nrEntries); return map; @@ -81,7 +81,7 @@ class HashMapTestSuite : public ::testing::Test { for (int i = 0; i < nrEntries; ++i) { celix_longHashMap_putLong(map, i, i); EXPECT_EQ(i, celix_longHashMap_getLong(map, i, 0)); - EXPECT_EQ(celix_longHashMap_size(map), i+1); + EXPECT_EQ(celix_longHashMap_size(map), i + 1); } } @@ -90,13 +90,14 @@ class HashMapTestSuite : public ::testing::Test { * This assumes that the map is filled using fillLongHashMap. */ void testGetEntriesFromLongMap(celix_long_hash_map_t* map, int nrOfEntriesToTest) { - std::uniform_int_distribution keyDistribution{0, (long)celix_longHashMap_size(map)-1}; - for (int i = 0; i< nrOfEntriesToTest; ++i) { + std::uniform_int_distribution keyDistribution{0, (long)celix_longHashMap_size(map) - 1}; + for (int i = 0; i < nrOfEntriesToTest; ++i) { long rand = keyDistribution(generator); EXPECT_EQ(celix_longHashMap_getLong(map, rand, 0), rand); } } -private: + + private: std::default_random_engine generator{}; }; @@ -190,12 +191,12 @@ TEST_F(HashMapTestSuite, DestroyHashMapWithSimpleRemovedCallback) { bool removed = celix_stringHashMap_remove(sMap, "key3"); EXPECT_TRUE(removed); - removed = celix_stringHashMap_remove(sMap, "key3"); //double remove + removed = celix_stringHashMap_remove(sMap, "key3"); // double remove EXPECT_FALSE(removed); removed = celix_stringHashMap_remove(sMap, nullptr); EXPECT_TRUE(removed); - removed = celix_stringHashMap_remove(sMap, nullptr); //double remove + removed = celix_stringHashMap_remove(sMap, nullptr); // double remove EXPECT_FALSE(removed); EXPECT_EQ(celix_stringHashMap_size(sMap), 3); @@ -225,7 +226,7 @@ TEST_F(HashMapTestSuite, ReplaceAndClearHashMapWithRemovedCallback) { std::atomic count{0}; celix_string_hash_map_create_options_t sOpts{}; sOpts.removedCallbackData = &count; - sOpts.removedCallback = [](void *data, const char* key, celix_hash_map_value_t value) { + sOpts.removedCallback = [](void* data, const char* key, celix_hash_map_value_t value) { auto* c = static_cast*>(data); if (celix_utils_stringEquals(key, "key1")) { c->fetch_add(1); @@ -237,10 +238,10 @@ TEST_F(HashMapTestSuite, ReplaceAndClearHashMapWithRemovedCallback) { }; auto* sMap = celix_stringHashMap_createWithOptions(&sOpts); celix_stringHashMap_putLong(sMap, "key1", 1); - celix_stringHashMap_putLong(sMap, "key1", 2); //replacing old value, count +1 + celix_stringHashMap_putLong(sMap, "key1", 2); // replacing old value, count +1 celix_stringHashMap_putLong(sMap, "key2", 3); - celix_stringHashMap_putLong(sMap, "key2", 4); //replacing old value, count +1 - celix_stringHashMap_clear(sMap); //count +2 + celix_stringHashMap_putLong(sMap, "key2", 4); // replacing old value, count +1 + celix_stringHashMap_clear(sMap); // count +2 EXPECT_EQ(count.load(), 4); EXPECT_EQ(celix_stringHashMap_size(sMap), 0); celix_stringHashMap_destroy(sMap); @@ -248,7 +249,7 @@ TEST_F(HashMapTestSuite, ReplaceAndClearHashMapWithRemovedCallback) { count = 0; celix_long_hash_map_create_options_t lOpts{}; lOpts.removedCallbackData = &count; - lOpts.removedCallback = [](void *data, long key, celix_hash_map_value_t value) { + lOpts.removedCallback = [](void* data, long key, celix_hash_map_value_t value) { auto* c = static_cast*>(data); if (key == 1) { c->fetch_add(1); @@ -260,16 +261,15 @@ TEST_F(HashMapTestSuite, ReplaceAndClearHashMapWithRemovedCallback) { }; auto* lMap = celix_longHashMap_createWithOptions(&lOpts); celix_longHashMap_putLong(lMap, 1, 1); - celix_longHashMap_putLong(lMap, 1, 2); //replacing old value, count +1 + celix_longHashMap_putLong(lMap, 1, 2); // replacing old value, count +1 celix_longHashMap_putLong(lMap, 2, 3); - celix_longHashMap_putLong(lMap, 2, 4); //replacing old value, count +1 - celix_longHashMap_clear(lMap); //count +2 + celix_longHashMap_putLong(lMap, 2, 4); // replacing old value, count +1 + celix_longHashMap_clear(lMap); // count +2 EXPECT_EQ(count.load(), 4); EXPECT_EQ(celix_longHashMap_size(lMap), 0); celix_longHashMap_destroy(lMap); } - TEST_F(HashMapTestSuite, HashMapClearTests) { auto* sMap = createStringHashMap(3); EXPECT_EQ(celix_stringHashMap_size(sMap), 3); @@ -284,13 +284,12 @@ TEST_F(HashMapTestSuite, HashMapClearTests) { celix_longHashMap_destroy(lMap); } - -template +template void testMapsWithValues(const std::vector>& values) { auto* sMap = celix_stringHashMap_create(); auto* lMap = celix_longHashMap_create(); - //test hashmap put + // test hashmap put for (const auto& tuple : values) { if constexpr (std::is_same_v) { auto status = celix_stringHashMap_put(sMap, std::get<0>(tuple).c_str(), std::get<2>(tuple)); @@ -323,7 +322,7 @@ void testMapsWithValues(const std::vector>& val ASSERT_EQ(values.size(), celix_stringHashMap_size(sMap)); ASSERT_EQ(values.size(), celix_longHashMap_size(lMap)); - //test hashmap get + // test hashmap get for (const auto& tuple : values) { if constexpr (std::is_same_v) { auto* value = celix_stringHashMap_get(sMap, std::get<0>(tuple).c_str()); @@ -354,7 +353,7 @@ void testMapsWithValues(const std::vector>& val } } - //test hashmap remove + // test hashmap remove for (const auto& tuple : values) { celix_stringHashMap_remove(sMap, std::get<0>(tuple).c_str()); celix_longHashMap_remove(lMap, std::get<1>(tuple)); @@ -368,30 +367,30 @@ void testMapsWithValues(const std::vector>& val TEST_F(HashMapTestSuite, PutAndGetWithDifferentValueTypes) { std::vector> values1{ - {"key1", 1, (void*)0x42}, - {"key2", 2, (void*)0x43}, - {"key3", 3, (void*)0x44}, + {"key1", 1, (void*)0x42}, + {"key2", 2, (void*)0x43}, + {"key3", 3, (void*)0x44}, }; testMapsWithValues(values1); std::vector> values2{ - {"key1", 1, 1}, - {"key2", 2, 2}, - {"key3", 3, 3}, + {"key1", 1, 1}, + {"key2", 2, 2}, + {"key3", 3, 3}, }; testMapsWithValues(values2); std::vector> values3{ - {"key1", 1, 1.0}, - {"key2", 2, 2.0}, - {"key3", 3, 3.0}, + {"key1", 1, 1.0}, + {"key2", 2, 2.0}, + {"key3", 3, 3.0}, }; testMapsWithValues(values3); std::vector> values4{ - {"key1", 1, true}, - {"key2", 2, false}, - {"key3", 3, true}, + {"key1", 1, true}, + {"key2", 2, false}, + {"key3", 3, true}, }; testMapsWithValues(values4); } @@ -412,7 +411,6 @@ TEST_F(HashMapTestSuite, GetWithFallbackValues) { celix_longHashMap_destroy(lMap); } - TEST_F(HashMapTestSuite, IterateTest) { auto* sMap = createStringHashMap(2); size_t count = 0; @@ -450,19 +448,15 @@ TEST_F(HashMapTestSuite, IterateStressTest) { auto* sMap = createStringHashMap(testCount); EXPECT_EQ(testCount, celix_stringHashMap_size(sMap)); int count = 0; - CELIX_STRING_HASH_MAP_ITERATE(sMap, iter) { - EXPECT_EQ(iter.index, count++); - } + CELIX_STRING_HASH_MAP_ITERATE(sMap, iter) { EXPECT_EQ(iter.index, count++); } EXPECT_EQ(testCount, count); testGetEntriesFromStringMap(sMap, 100); celix_stringHashMap_destroy(sMap); - auto *lMap = createLongHashMap(testCount); + auto* lMap = createLongHashMap(testCount); EXPECT_EQ(testCount, celix_longHashMap_size(lMap)); count = 0; - CELIX_LONG_HASH_MAP_ITERATE(lMap, iter) { - EXPECT_EQ(iter.index, count++); - } + CELIX_LONG_HASH_MAP_ITERATE(lMap, iter) { EXPECT_EQ(iter.index, count++); } EXPECT_EQ(testCount, count); testGetEntriesFromLongMap(lMap, 100); celix_longHashMap_destroy(lMap); @@ -470,35 +464,33 @@ TEST_F(HashMapTestSuite, IterateStressTest) { TEST_F(HashMapTestSuite, IterateStressCapacityAndLoadFactorTest) { celix_string_hash_map_create_options sOpts{}; - sOpts.loadFactor = 10; //high load factor to ensure buckets have multiple entries for testing + sOpts.loadFactor = 10; // high load factor to ensure buckets have multiple entries for testing sOpts.initialCapacity = 5; auto* sMap = celix_stringHashMap_createWithOptions(&sOpts); fillStringHashMap(sMap, 100); long value = celix_stringHashMap_getLong(sMap, "key50", 0); EXPECT_EQ(50, value); - //remove last 50 entries + // remove last 50 entries for (long i = 50; i < 100; ++i) { auto key = std::string{"key"} + std::to_string(i); celix_stringHashMap_remove(sMap, key.c_str()); } - testGetEntriesFromStringMap(sMap, 50); //test entry 0-49 + testGetEntriesFromStringMap(sMap, 50); // test entry 0-49 celix_stringHashMap_destroy(sMap); - celix_long_hash_map_create_options lOpts{}; - lOpts.loadFactor = 10; //high load factor to ensure buckets have multiple entries for testing + lOpts.loadFactor = 10; // high load factor to ensure buckets have multiple entries for testing lOpts.initialCapacity = 5; auto* lMap = celix_longHashMap_createWithOptions(&lOpts); fillLongHashMap(lMap, 100); value = celix_longHashMap_getLong(lMap, 50, 0); EXPECT_EQ(50, value); - //remove last 50 entries + // remove last 50 entries for (long i = 50; i < 100; ++i) { celix_longHashMap_remove(lMap, i); } testGetEntriesFromLongMap(lMap, 50); celix_longHashMap_destroy(lMap); - } TEST_F(HashMapTestSuite, IterateWithRemoveTest) { @@ -506,7 +498,7 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { auto iter1 = celix_stringHashMap_begin(sMap); while (!celix_stringHashMapIterator_isEnd(&iter1)) { if (iter1.index % 2 == 0) { - //note only removing entries where the iter key is even + // note only removing entries where the iter key is even celix_stringHashMapIterator_remove(&iter1); } else { celix_stringHashMapIterator_next(&iter1); @@ -522,7 +514,7 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { auto iter2 = celix_longHashMap_begin(lMap); while (!celix_longHashMapIterator_isEnd(&iter2)) { if (iter2.index % 2 == 0) { - //note only removing entries where the iter index is even + // note only removing entries where the iter index is even celix_longHashMapIterator_remove(&iter2); } else { celix_longHashMapIterator_next(&iter2); @@ -530,7 +522,7 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { } EXPECT_EQ(3, celix_longHashMap_size(lMap)); EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); - celix_longHashMapIterator_next(&iter2); //calling next on end iter, does nothing + celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); celix_longHashMap_destroy(lMap); } @@ -561,7 +553,6 @@ TEST_F(HashMapTestSuite, IterateEndTest) { celix_longHashMap_destroy(lMap2); } - TEST_F(HashMapTestSuite, EqualsTest) { auto* sMap = createStringHashMap(2); auto sIter1 = celix_stringHashMap_begin(sMap); @@ -578,9 +569,8 @@ TEST_F(HashMapTestSuite, EqualsTest) { celix_stringHashMapIterator_next(&sIter2); EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2)); - - //Same for long hash map - auto *lMap = createLongHashMap(1); + // Same for long hash map + auto* lMap = createLongHashMap(1); auto lIter1 = celix_longHashMap_begin(lMap); auto lIter2 = celix_longHashMap_begin(lMap); @@ -606,9 +596,8 @@ TEST_F(HashMapTestSuite, EqualsZeroSizeMapTest) { auto sEnd = celix_stringHashMap_end(sMap); EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sEnd)); - // Because map size is 0, begin iter should equal end iter - auto *lMap = createLongHashMap(0); + auto* lMap = createLongHashMap(0); auto lIter1 = celix_longHashMap_begin(lMap); auto lEnd = celix_longHashMap_end(lMap); EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lEnd)); @@ -626,23 +615,58 @@ TEST_F(HashMapTestSuite, StoreKeysWeaklyTest) { free(key); }; auto* sMap = celix_stringHashMap_createWithOptions(&opts); - EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); //new key -> takes ownership - EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key1", 2)); //replace key -> takes no ownership + EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); // new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key1", 2)); // replace key -> takes no ownership - EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key2"), 3)); //new key -> takes ownership - EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key2", 4)); //replace key -> takes no ownership + EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key2"), 3)); // new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key2", 4)); // replace key -> takes no ownership celix_stringHashMap_remove(sMap, "key1"); celix_stringHashMap_destroy(sMap); } - -TEST_F(HashMapTestSuite, StringHashMapCleanup) { +TEST_F(HashMapTestSuite, StringHashMapCleanupTest) { celix_autoptr(celix_string_hash_map_t) map = celix_stringHashMap_create(); EXPECT_NE(nullptr, map); } -TEST_F(HashMapTestSuite, LongHashMapCleanup) { +TEST_F(HashMapTestSuite, LongHashMapCleanupTest) { celix_autoptr(celix_long_hash_map_t) map = celix_longHashMap_create(); EXPECT_NE(nullptr, map); } + +TEST_F(HashMapTestSuite, StringHashMapEqualsTest) { + celix_autoptr(celix_string_hash_map_t) map1 = celix_stringHashMap_create(); + celix_autoptr(celix_string_hash_map_t) map2 = celix_stringHashMap_create(); + EXPECT_TRUE(celix_stringHashMap_equals(map1, map2)); + + celix_stringHashMap_putLong(map1, "key1", 42); + celix_stringHashMap_putBool(map1, "key2", true); + celix_stringHashMap_putLong(map2, "key1", 42); + celix_stringHashMap_putBool(map2, "key2", true); + EXPECT_TRUE(celix_stringHashMap_equals(map1, map2)); + + celix_stringHashMap_putLong(map2, "key3", 42); + EXPECT_FALSE(celix_stringHashMap_equals(map1, map2)); + + celix_stringHashMap_putLong(map1, "key3", 42); + EXPECT_TRUE(celix_stringHashMap_equals(map1, map2)); +} + +TEST_F(HashMapTestSuite, LongHashMapEqualsTest) { + celix_autoptr(celix_long_hash_map_t) map1 = celix_longHashMap_create(); + celix_autoptr(celix_long_hash_map_t) map2 = celix_longHashMap_create(); + EXPECT_TRUE(celix_longHashMap_equals(map1, map2)); + + celix_longHashMap_putLong(map1, 1, 42); + celix_longHashMap_putBool(map1, 2, true); + celix_longHashMap_putLong(map2, 1, 42); + celix_longHashMap_putBool(map2, 2, true); + EXPECT_TRUE(celix_longHashMap_equals(map1, map2)); + + celix_longHashMap_putLong(map2, 3, 42); + EXPECT_FALSE(celix_longHashMap_equals(map1, map2)); + + celix_longHashMap_putLong(map1, 3, 42); + EXPECT_TRUE(celix_longHashMap_equals(map1, map2)); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 0007a32bf..8edc242e8 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -105,7 +105,7 @@ TEST_F(PropertiesTestSuite, GetAsLongTest) { celix_properties_set(props, "t1", "42"); celix_properties_set(props, "t2", "-42"); celix_properties_set(props, "t3", ""); - celix_properties_set(props, "t4", "42 bla"); //converts to 42 + celix_properties_set(props, "t4", "42 bla"); //does not convert to 42 celix_properties_set(props, "t5", "bla"); long v = celix_properties_getAsLong(props, "t1", -1); @@ -118,7 +118,7 @@ TEST_F(PropertiesTestSuite, GetAsLongTest) { EXPECT_EQ(-1, v); v = celix_properties_getAsLong(props, "t4", -1); - EXPECT_EQ(42, v); + EXPECT_EQ(-1, v); v = celix_properties_getAsLong(props, "t5", -1); EXPECT_EQ(-1, v); @@ -197,7 +197,7 @@ TEST_F(PropertiesTestSuite, GetLongTest) { auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "2"); - celix_properties_set(properties, "b", "-10032L"); + celix_properties_set(properties, "b", "-10032"); celix_properties_set(properties, "c", ""); celix_properties_set(properties, "d", "garbage"); @@ -227,7 +227,7 @@ TEST_F(PropertiesTestSuite, GetAsDoubleTest) { auto* properties = celix_properties_create(); celix_properties_set(properties, "a", "2"); - celix_properties_set(properties, "b", "-10032L"); + celix_properties_set(properties, "b", "-10032"); celix_properties_set(properties, "c", "1.2"); celix_properties_setDouble(properties, "d", 1.4); celix_properties_set(properties, "e", ""); @@ -618,3 +618,33 @@ TEST_F(PropertiesTestSuite, NullArgumentsTest) { auto props = celix_properties_loadWithStream(nullptr); EXPECT_EQ(nullptr, props); } + +TEST_F(PropertiesTestSuite, PropertiesEqualsTest) { + EXPECT_TRUE(celix_properties_equals(nullptr, nullptr)); + + celix_autoptr(celix_properties_t) prop1 = celix_properties_create(); + EXPECT_FALSE(celix_properties_equals(prop1, nullptr)); + + celix_autoptr(celix_properties_t) prop2 = celix_properties_create(); + EXPECT_TRUE(celix_properties_equals(prop1, prop2)); + + celix_properties_set(prop1, "key1", "value1"); + celix_properties_setLong(prop1, "key2", 42); + celix_properties_set(prop2, "key1", "value1"); + celix_properties_setLong(prop2, "key2", 42); + EXPECT_TRUE(celix_properties_equals(prop1, prop2)); + + celix_properties_setBool(prop1, "key3", false); + EXPECT_FALSE(celix_properties_equals(prop1, prop2)); + celix_properties_setBool(prop2, "key3", false); + EXPECT_TRUE(celix_properties_equals(prop1, prop2)); + + celix_properties_setVersionWithoutCopy(prop1, "key4", celix_version_create(1,2,3, nullptr)); + EXPECT_FALSE(celix_properties_equals(prop1, prop2)); + celix_properties_setVersionWithoutCopy(prop2, "key4", celix_version_create(1,2,3, nullptr)); + EXPECT_TRUE(celix_properties_equals(prop1, prop2)); + + celix_properties_setLong(prop1, "key5", 42); + celix_properties_setDouble(prop2, "key5", 42.0); + EXPECT_FALSE(celix_properties_equals(prop1, prop2)); //different types +} diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 87aac9a14..fc981c386 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -286,6 +286,18 @@ CELIX_UTILS_EXPORT bool celix_longHashMap_remove(celix_long_hash_map_t* map, lon */ CELIX_UTILS_EXPORT void celix_longHashMap_clear(celix_long_hash_map_t* map); +/** + * @brief Check if the provided string hash maps are equal. + * + * Equals means that both maps have the same number of entries and that all entries in the first map + * are also present in the second map and have the same value. + * + * @param[in] map1 The first map to compare. + * @param[in] map2 The second map to compare. + * @return true if the maps are equal, false otherwise. + */ +CELIX_UTILS_EXPORT bool celix_longHashMap_equals(const celix_long_hash_map_t* map1, const celix_long_hash_map_t* map2); + /** * @brief Get an iterator pointing to the first element in the map. * diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index e6405372e..5738b6382 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -404,6 +404,18 @@ CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion(const celix_pr */ CELIX_UTILS_EXPORT int celix_properties_size(const celix_properties_t* properties); +/** + * @brief Check whether the provided property sets are equal. + * + * Equals means that both property sets have the same number of properties and that all properties in the first set + * are also present in the second set and have the same value. + * + * @param[in] props1 The first property set to compare. + * @param[in] props2 The second property set to compare. + * @return true if the property sets are equal, false otherwise. + */ +CELIX_UTILS_EXPORT bool celix_properties_equals(const celix_properties_t* props1, const celix_properties_t* props2); + /** * @brief Construct an iterator pointing to the first entry in the properties object. * diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 1097b109a..f3ce3e421 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -313,6 +313,18 @@ CELIX_UTILS_EXPORT bool celix_stringHashMap_remove(celix_string_hash_map_t* map, */ CELIX_UTILS_EXPORT void celix_stringHashMap_clear(celix_string_hash_map_t* map); +/** + * @brief Check if the provided string hash maps are equal. + * + * Equals means that both maps have the same number of entries and that all entries in the first map + * are also present in the second map and have the same value. + * + * @param[in] map1 The first map to compare. + * @param[in] map2 The second map to compare. + * @return true if the maps are equal, false otherwise. + */ +CELIX_UTILS_EXPORT bool celix_stringHashMap_equals(const celix_string_hash_map_t* map1, const celix_string_hash_map_t* map2); + /** * @brief Get an iterator pointing to the first element in the map. * @@ -346,6 +358,7 @@ CELIX_UTILS_EXPORT void celix_stringHashMapIterator_next(celix_string_hash_map_i /** * @brief Compares two celix_string_hash_map_iterator_t objects for equality. + * * @param[in] iterator The first iterator to compare. * @param[in] other The second iterator to compare. * @return true if the iterators point to the same entry in the same hash map, false otherwise. diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index f7b8bb516..58ca3f4e8 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -83,11 +83,11 @@ static unsigned int celix_longHashMap_hash(const celix_hash_map_key_t* key) { return key->longKey ^ (key->longKey >> (sizeof(key->longKey)*8/2)); } -static bool celix_stringHashMap_equals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { +static bool celix_stringHashMap_keyEquals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { return celix_utils_stringEquals(key1->strKey, key2->strKey); } -static bool celix_longHashMap_equals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { +static bool celix_longHashMap_keyEquals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { return key1->longKey == key2->longKey; } @@ -269,24 +269,28 @@ static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v, void** previousValueOut) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(value)); value.ptrValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } static celix_status_t celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v, long* previousValueOut) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(value)); value.longValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } static celix_status_t celix_hashMap_putDouble(celix_hash_map_t* map, const char* strKey, long longKey, double v) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(value)); value.doubleValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } static celix_status_t celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v, bool* previousValueOut) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(value)); value.boolValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } @@ -425,7 +429,7 @@ celix_string_hash_map_t* celix_stringHashMap_createWithOptions(const celix_strin unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_equals); + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_keyEquals); if (status != CELIX_SUCCESS) { celix_err_push("Cannot initialize hash map"); return NULL; @@ -455,7 +459,7 @@ celix_long_hash_map_t* celix_longHashMap_createWithOptions(const celix_long_hash unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_equals); + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_keyEquals); if (status != CELIX_SUCCESS) { celix_err_push("Cannot initialize hash map"); return NULL; @@ -587,6 +591,33 @@ void celix_longHashMap_clear(celix_long_hash_map_t* map) { celix_hashMap_clear(&map->genericMap); } +static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_map_t* map2) { + if (map1 == NULL && map2 == NULL) { + return true; + } + if (map1 == NULL || map2 == NULL) { + return false; + } + if (map1->size != map2->size) { + return false; + } + for (celix_hash_map_entry_t* entry = celix_hashMap_firstEntry(map1); entry != NULL; entry = celix_hashMap_nextEntry(map1, entry)) { + void* value = celix_hashMap_get(map2, entry->key.strKey, entry->key.longKey); + if (value == NULL || memcmp(&entry->value, value, sizeof(entry->value)) != 0) { + return false; + } + } + return true; +} + +bool celix_stringHashMap_equals(const celix_string_hash_map_t* map1, const celix_string_hash_map_t* map2) { + return celix_hashMap_equals(&map1->genericMap, &map2->genericMap); +} + +bool celix_longHashMap_equals(const celix_long_hash_map_t* map1, const celix_long_hash_map_t* map2) { + return celix_hashMap_equals(&map1->genericMap, &map2->genericMap); +} + celix_string_hash_map_iterator_t celix_stringHashMap_begin(const celix_string_hash_map_t* map) { celix_string_hash_map_iterator_t iter; memset(&iter, 0, sizeof(iter)); diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 3fb8f04d9..fda3fe9af 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -34,6 +34,7 @@ #include "celix_string_hash_map.h" #include "celix_utils.h" #include "celix_stdlib_cleanup.h" +#include "celix_convert_utils.h" #define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 512 #define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16 @@ -702,6 +703,25 @@ celix_properties_setEntry(celix_properties_t* properties, const char* key, const return CELIX_SUCCESS; // silently ignore NULL entry } +static bool celix_properties_entryEquals(const celix_properties_entry_t* entry1, + const celix_properties_entry_t* entry2) { + if (entry1->valueType != entry2->valueType) { + return false; + } + switch (entry1->valueType) { + case CELIX_PROPERTIES_VALUE_TYPE_LONG: + return entry1->typed.longValue == entry2->typed.longValue; + case CELIX_PROPERTIES_VALUE_TYPE_DOUBLE: + return entry1->typed.doubleValue == entry2->typed.doubleValue; + case CELIX_PROPERTIES_VALUE_TYPE_BOOL: + return entry1->typed.boolValue == entry2->typed.boolValue; + case CELIX_PROPERTIES_VALUE_TYPE_VERSION: + return celix_version_compareTo(entry1->typed.versionValue, entry2->typed.versionValue) == 0; + default: // STRING + return strcmp(entry1->value, entry2->value) == 0; + } +} + void celix_properties_unset(celix_properties_t* properties, const char* key) { if (properties != NULL) { celix_stringHashMap_remove(properties->map, key); @@ -709,19 +729,15 @@ void celix_properties_unset(celix_properties_t* properties, const char* key) { } long celix_properties_getAsLong(const celix_properties_t* props, const char* key, long defaultValue) { - long result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { return entry->typed.longValue; + } else if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { + return (long)entry->typed.doubleValue; } else if (entry != NULL) { - char* enptr = NULL; - errno = 0; - long r = strtol(entry->value, &enptr, 10); - if (enptr != entry->value && errno == 0) { - result = r; - } + return celix_utils_convertStringToLong(entry->value, defaultValue, NULL); } - return result; + return defaultValue; } celix_status_t celix_properties_setLong(celix_properties_t* props, const char* key, long value) { @@ -729,19 +745,13 @@ celix_status_t celix_properties_setLong(celix_properties_t* props, const char* k } double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) { - double result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { return entry->typed.doubleValue; } else if (entry != NULL) { - char* enptr = NULL; - errno = 0; - double r = strtod(entry->value, &enptr); - if (enptr != entry->value && errno == 0) { - result = r; - } + return celix_utils_convertStringToDouble(entry->value, defaultValue, NULL); } - return result; + return defaultValue; } celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* key, double val) { @@ -749,21 +759,13 @@ celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* } bool celix_properties_getAsBool(const celix_properties_t* props, const char* key, bool defaultValue) { - bool result = defaultValue; celix_properties_entry_t* entry = celix_properties_getEntry(props, key); if (entry != NULL && entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { return entry->typed.boolValue; } else if (entry != NULL) { - char buf[32]; - snprintf(buf, 32, "%s", entry->value); - char* trimmed = celix_utils_trimInPlace(buf); - if (strncasecmp("true", trimmed, strlen("true")) == 0) { - result = true; - } else if (strncasecmp("false", buf, strlen("false")) == 0) { - result = false; - } + return celix_utils_convertStringToBool(entry->value, defaultValue, NULL); } - return result; + return defaultValue; } celix_status_t celix_properties_setBool(celix_properties_t* props, const char* key, bool val) { @@ -809,6 +811,25 @@ int celix_properties_size(const celix_properties_t* properties) { return (int)celix_stringHashMap_size(properties->map); } +bool celix_properties_equals(const celix_properties_t* props1, const celix_properties_t* props2) { + if (props1 == NULL && props2 == NULL) { + return true; + } + if (props1 == NULL || props2 == NULL) { + return false; + } + if (celix_properties_size(props1) != celix_properties_size(props2)) { + return false; + } + CELIX_PROPERTIES_ITERATE(props1, iter) { + celix_properties_entry_t* entry2 = celix_properties_getEntry(props2, iter.key); + if (entry2 == NULL || !celix_properties_entryEquals(&iter.entry, entry2)) { + return false; + } + } + return true; +} + typedef struct { celix_string_hash_map_iterator_t mapIter; const celix_properties_t* props; From 27e9e78d185259d3f008b36ca7d4fdb67d4aec9a Mon Sep 17 00:00:00 2001 From: PengZheng Date: Wed, 1 Nov 2023 19:24:55 +0800 Subject: [PATCH 32/63] Link test_utils_cxx17tests into test_utils. --- documents/framework.md | 2 +- libs/utils/gtest/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/documents/framework.md b/documents/framework.md index 91d9dbb4f..6825adb53 100644 --- a/documents/framework.md +++ b/documents/framework.md @@ -303,4 +303,4 @@ The following framework properties are supported: | CELIX_FRAMEWORK_AUTO_START_5 | "" | The bundles to install and start after the framework is started. Multiple bundles can be provided separated by a space. | | CELIX_AUTO_INSTALL | "" | The bundles to install after the framework is started. Multiple bundles can be provided separated by a space. | | CELIX_LOGGING_DEFAULT_ACTIVE_LOG_LEVEL | "info" | The default active log level for created log services. Possible values are "trace", "debug", "info", "warning", "error" and "fatal". | - | CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS | "2" | The allowed processing time for scheduled events in seconds, if processing takes longer a warning message will be logged. | +| CELIX_ALLOWED_PROCESSING_TIME_FOR_SCHEDULED_EVENT_IN_SECONDS | "2" | The allowed processing time for scheduled events in seconds, if processing takes longer a warning message will be logged. | diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 4899cc895..4a340d555 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils) configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY) if (CELIX_CXX17) - add_library(test_utils_cxx17tests STATIC + add_library(test_utils_cxx17tests OBJECT src/ArrayListTestSuite.cc #Uses constexpr src/HashMapTestSuite.cc #Uses constexpr ) From 833a5c8f9b76e099071e451d4ee6891f94167851 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sun, 5 Nov 2023 12:30:22 +0800 Subject: [PATCH 33/63] Remove unused functions and fix typo. --- libs/utils/include/celix_properties.h | 2 +- libs/utils/include/celix_version.h | 4 ---- libs/utils/src/version.c | 4 ---- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 5738b6382..f106b081f 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -99,7 +99,7 @@ typedef struct celix_properties_iterator { size_t index; /** - * @brief Te current key. + * @brief The current key. */ const char* key; diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index 143043215..8b713ee1e 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -62,10 +62,6 @@ typedef struct celix_version celix_version_t; */ CELIX_UTILS_EXPORT celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier); -/** - * @deprecated Use celix_version_create instead. - */ -CELIX_UTILS_DEPRECATED_EXPORT celix_version_t* celixversion_createVersion(int major, int minor, int micro, const char* qualifier); CELIX_UTILS_EXPORT void celix_version_destroy(celix_version_t* version); diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index 814caccd9..2c1d01fc8 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -97,10 +97,6 @@ celix_status_t version_isCompatible(version_pt user, version_pt provider, bool* return CELIX_SUCCESS; } -celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char * qualifier) { - return celix_version_create(major, minor, micro, qualifier); -} - celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier) { if (major < 0 || minor < 0 || micro < 0) { return NULL; From b5f25b52eaee7e3d68813b2fbcb9b4a20c41ca9e Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 6 Nov 2023 20:23:51 +0100 Subject: [PATCH 34/63] #509: Fix celix string/long hashmap equals functions --- libs/utils/gtest/src/HashMapTestSuite.cc | 20 ++++++++++++---- libs/utils/include/celix_string_hash_map.h | 4 ++++ libs/utils/src/celix_hash_map.c | 27 +++++++++++++++------- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index fed19b5e3..901495300 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -615,11 +615,15 @@ TEST_F(HashMapTestSuite, StoreKeysWeaklyTest) { free(key); }; auto* sMap = celix_stringHashMap_createWithOptions(&opts); - EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); // new key -> takes ownership - EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key1", 2)); // replace key -> takes no ownership - - EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key2"), 3)); // new key -> takes ownership - EXPECT_TRUE(celix_stringHashMap_putLong(sMap, "key2", 4)); // replace key -> takes no ownership + EXPECT_FALSE(celix_stringHashMap_hasKey(sMap, "key1")); + EXPECT_EQ(CELIX_SUCCESS, celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); // new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_hasKey(sMap, "key1")); + EXPECT_EQ(CELIX_SUCCESS, celix_stringHashMap_putLong(sMap, "key1", 2)); // replace key -> takes no ownership + + EXPECT_FALSE(celix_stringHashMap_hasKey(sMap, "key2")); + EXPECT_EQ(CELIX_SUCCESS, celix_stringHashMap_putLong(sMap, celix_utils_strdup("key2"), 3)); // new key -> takes ownership + EXPECT_TRUE(celix_stringHashMap_hasKey(sMap, "key2")); + EXPECT_EQ(CELIX_SUCCESS, celix_stringHashMap_putLong(sMap, "key2", 4)); // replace key -> takes no ownership celix_stringHashMap_remove(sMap, "key1"); celix_stringHashMap_destroy(sMap); @@ -639,6 +643,9 @@ TEST_F(HashMapTestSuite, StringHashMapEqualsTest) { celix_autoptr(celix_string_hash_map_t) map1 = celix_stringHashMap_create(); celix_autoptr(celix_string_hash_map_t) map2 = celix_stringHashMap_create(); EXPECT_TRUE(celix_stringHashMap_equals(map1, map2)); + EXPECT_FALSE(celix_stringHashMap_equals(map1, nullptr)); + EXPECT_FALSE(celix_stringHashMap_equals(nullptr, map2)); + EXPECT_TRUE(celix_stringHashMap_equals(nullptr, nullptr)); celix_stringHashMap_putLong(map1, "key1", 42); celix_stringHashMap_putBool(map1, "key2", true); @@ -657,6 +664,9 @@ TEST_F(HashMapTestSuite, LongHashMapEqualsTest) { celix_autoptr(celix_long_hash_map_t) map1 = celix_longHashMap_create(); celix_autoptr(celix_long_hash_map_t) map2 = celix_longHashMap_create(); EXPECT_TRUE(celix_longHashMap_equals(map1, map2)); + EXPECT_FALSE(celix_longHashMap_equals(map1, nullptr)); + EXPECT_FALSE(celix_longHashMap_equals(nullptr, map2)); + EXPECT_TRUE(celix_longHashMap_equals(nullptr, nullptr)); celix_longHashMap_putLong(map1, 1, 42); celix_longHashMap_putBool(map1, 2, true); diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index f3ce3e421..72fb869f7 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -319,6 +319,8 @@ CELIX_UTILS_EXPORT void celix_stringHashMap_clear(celix_string_hash_map_t* map); * Equals means that both maps have the same number of entries and that all entries in the first map * are also present in the second map and have the same value. * + * The value are compared using memcpy, so for pointer values the pointer value is compared and not the value itself. + * * @param[in] map1 The first map to compare. * @param[in] map2 The second map to compare. * @return true if the maps are equal, false otherwise. @@ -359,6 +361,8 @@ CELIX_UTILS_EXPORT void celix_stringHashMapIterator_next(celix_string_hash_map_i /** * @brief Compares two celix_string_hash_map_iterator_t objects for equality. * + * The value are compared using memcpy, so for pointer values the pointer value is compared and not the value itself. + * * @param[in] iterator The first iterator to compare. * @param[in] other The second iterator to compare. * @return true if the iterators point to the same entry in the same hash map, false otherwise. diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 58ca3f4e8..7fe4f7124 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -592,18 +592,14 @@ void celix_longHashMap_clear(celix_long_hash_map_t* map) { } static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_map_t* map2) { - if (map1 == NULL && map2 == NULL) { - return true; - } - if (map1 == NULL || map2 == NULL) { - return false; - } if (map1->size != map2->size) { return false; } for (celix_hash_map_entry_t* entry = celix_hashMap_firstEntry(map1); entry != NULL; entry = celix_hashMap_nextEntry(map1, entry)) { - void* value = celix_hashMap_get(map2, entry->key.strKey, entry->key.longKey); - if (value == NULL || memcmp(&entry->value, value, sizeof(entry->value)) != 0) { + celix_hash_map_entry_t* entryMap2 = celix_hashMap_getEntry(map2, entry->key.strKey, entry->key.longKey); + + //note using memcmp, so for void* values the pointer value is compared, not the value itself. + if (entryMap2 == NULL || memcmp(&entryMap2->value, &entry->value, sizeof(entryMap2->value)) != 0) { return false; } } @@ -611,10 +607,25 @@ static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_ } bool celix_stringHashMap_equals(const celix_string_hash_map_t* map1, const celix_string_hash_map_t* map2) { + if (map1 == NULL && map2 == NULL) { + return true; + } + if (map1 == NULL || map2 == NULL) { + return false; + } return celix_hashMap_equals(&map1->genericMap, &map2->genericMap); } bool celix_longHashMap_equals(const celix_long_hash_map_t* map1, const celix_long_hash_map_t* map2) { + if (map1 == NULL && map2 == NULL) { + return true; + } + if (map1 == NULL || map2 == NULL) { + return false; + } + if (map1->genericMap.size != map2->genericMap.size) { + return false; + } return celix_hashMap_equals(&map1->genericMap, &map2->genericMap); } From 737773b25fd5ecc3dbdda8bcdb9c26b5fc4a455e Mon Sep 17 00:00:00 2001 From: PengZheng Date: Tue, 7 Nov 2023 22:54:00 +0800 Subject: [PATCH 35/63] Minor typo fix and return value improvement. --- libs/utils/src/properties.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index fda3fe9af..0c36d0f49 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -99,7 +99,7 @@ void properties_store(properties_pt properties, const char* filename, const char celix_status_t properties_copy(properties_pt properties, properties_pt* out) { celix_properties_t* copy = celix_properties_copy(properties); *out = copy; - return copy == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS; + return copy == NULL ? CELIX_ENOMEM : CELIX_SUCCESS; } const char* properties_get(properties_pt properties, const char* key) { @@ -133,7 +133,7 @@ static void updateBuffers(char** key, char** value, char** output, int outputPos } /** - * Create a new string from the provided str by either using strup or storing the string the short properties + * Create a new string from the provided str by either using strdup or storing the string the short properties * optimization string buffer. */ char* celix_properties_createString(celix_properties_t* properties, const char* str) { From 71742e8e9556dcf136915538626c8d95f88a354b Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 9 Nov 2023 18:15:44 +0100 Subject: [PATCH 36/63] #509: Remove string_view usage from Version.h and remove deprecated Properties cast --- libs/utils/include/celix/Properties.h | 14 +---------- libs/utils/include/celix/Version.h | 34 +++++++++++++++++---------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 7262733d1..6037d98e1 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -90,7 +90,7 @@ namespace celix { /** * @brief A collection of strings key values mainly used as meta data for registered services. * - * @note Provided `const char*` and `std::string_view` values must be null terminated strings. + * @note Provided `const char*` values must be null terminated strings. * @note Not thread safe. */ class Properties { @@ -499,18 +499,6 @@ namespace celix { return result; } -#ifdef CELIX_PROPERTIES_ALLOW_IMPLICIT_MAP_CAST - /** - * @brief cast the celix::Properties to a std::string, std::string map. - * @warning This method is added to ensure backwards compatibility with the celix::dm::Properties, but the - * use of this cast should be avoided. - * This method will eventually be removed. - */ - operator std::map() const { - return convertToMap(); - } -#endif - /** * @brief Store the property set to the given file path. * diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h index c019d14a9..8fb19449b 100644 --- a/libs/utils/include/celix/Version.h +++ b/libs/utils/include/celix/Version.h @@ -40,7 +40,9 @@ namespace celix { class Version { public: - ///@brief Constructs a new empty version with all components set to zero. + /** + * @brief Constructs a new empty version with all components set to zero. + */ Version() : cVersion{createVersion(celix_version_createEmptyVersion())}, qualifier{celix_version_getQualifier(cVersion.get())} {} @@ -52,34 +54,40 @@ namespace celix { * @param micro The micro component of the version. * @param qualifier The qualifier string of the version. */ -#if __cplusplus >= 201703L //C++17 or higher - Version(int major, int minor, int micro, std::string_view qualifier = {}) : - cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))}, - qualifier{celix_version_getQualifier(cVersion.get())} {} -#else explicit Version(int major, int minor, int micro, const std::string& qualifier = {}) : cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))}, qualifier{celix_version_getQualifier(cVersion.get())} {} -#endif - ///@brief Move-constructs a new version from an existing one. + /** + * @brief Move-constructs a new version from an existing one. + */ Version(Version&&) = default; - ///@brief Copy constructor for a Celix Version object. + /** + * @brief Copy constructor for a Celix Version object. + */ Version(const Version& rhs) = default; - ///@brief Move assignment operator for the Celix Version class. + /** + * @brief Move assignment operator for the Celix Version class. + */ Version& operator=(Version&&) = default; - ///@brief Copy assignment operator for the Celix Version class. + /** + * @brief Copy assignment operator for the Celix Version class. + */ Version& operator=(const Version& rhs) = default; - ///@brief Test whether two Version objects are equal. + /** + * @brief Test whether two Version objects are equal. + */ bool operator==(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) == 0; } - ///@brief Overload the < operator to compare two Version objects. + /** + * @brief Overload the < operator to compare two Version objects. + */ bool operator<(const Version& rhs) const { return celix_version_compareTo(cVersion.get(), rhs.cVersion.get()) < 0; } From 701b5c97ffb02db20fcd78649a536099a9b50060 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 9 Nov 2023 19:15:04 +0100 Subject: [PATCH 37/63] #509: Add the celix properties optimization cache sizes as configurable build options. --- conanfile.py | 21 +++++++---- libs/utils/CMakeLists.txt | 7 ++++ libs/utils/gtest/src/CxxVersionTestSuite.cc | 8 ----- .../src/PropertiesErrorInjectionTestSuite.cc | 14 ++++++-- .../utils/src/celix_properties_constants.h.in | 35 +++++++++++++++++++ libs/utils/src/properties.c | 20 +++++------ 6 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 libs/utils/src/celix_properties_constants.h.in diff --git a/conanfile.py b/conanfile.py index f1d242112..877ab7fd4 100644 --- a/conanfile.py +++ b/conanfile.py @@ -102,9 +102,13 @@ class CelixConan(ConanFile): } options = { "celix_err_buffer_size": ["ANY"], + "celix_properties_optimization_string_buffer_size": ["ANY"], + "celix_properties_optimization_entries_buffer_size": ["ANY"], } default_options = { "celix_err_buffer_size": "512", + "celix_properties_optimization_string_buffer_size": "128", + "celix_properties_optimization_entries_buffer_size": "16", } for comp in _celix_defaults.keys(): @@ -113,6 +117,14 @@ class CelixConan(ConanFile): _cmake = None + def validate_config_option_is_positive_number(self, option): + try: + val = int(self.options.get_safe(option)) + if val <= 0: + raise ValueError + except ValueError: + raise ConanInvalidConfiguration("{} must be a positive number".format(option)) + def validate(self): if self.settings.os != "Linux" and self.settings.os != "Macos": raise ConanInvalidConfiguration("Celix is only supported for Linux/Macos") @@ -123,12 +135,9 @@ def validate(self): if self.options.build_rsa_discovery_zeroconf and self.settings.os != "Linux": raise ConanInvalidConfiguration("Celix build_rsa_discovery_zeroconf is only supported for Linux") - try: - val = int(self.options.celix_err_buffer_size) - if val <= 0: - raise ValueError - except ValueError: - raise ConanInvalidConfiguration("celix_err_buffer_size must be a positive number") + self.validate_config_option_is_positive_number("celix_err_buffer_size") + self.validate_config_option_is_positive_number("celix_properties_optimization_string_buffer_size") + self.validate_config_option_is_positive_number("celix_properties_optimization_entries_buffer_size") def package_id(self): del self.info.options.build_all diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index c3c0dac8c..f937ab5d6 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -91,11 +91,17 @@ if (UTILS) BASE_NAME "CELIX_UTILS" EXPORT_FILE_NAME "${CMAKE_BINARY_DIR}/celix/gen/includes/utils/celix_utils_export.h") target_include_directories(utils PUBLIC $) + target_include_directories(utils PRIVATE $) #Configure celix_err_constant header file set(CELIX_ERR_BUFFER_SIZE 512 CACHE STRING "The size of the thread-specific buffer used for library error messages") configure_file("${CMAKE_CURRENT_LIST_DIR}/src/celix_err_constants.h.in" "${CMAKE_BINARY_DIR}/celix/gen/includes/utils/celix_err_constants.h" @ONLY) + #Configure celix_properties_constant (private) header file + set(CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 128 CACHE STRING "The string optimization buffer size used for properties") + set(CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE 16 CACHE STRING "The entries optimization buffer size used for properties") + configure_file("${CMAKE_CURRENT_LIST_DIR}/src/celix_properties_constants.h.in" "${CMAKE_BINARY_DIR}/celix/gen/src/utils/celix_properties_constants.h" @ONLY) + install(TARGETS utils EXPORT celix LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT framework INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/utils) install(DIRECTORY include/ @@ -122,6 +128,7 @@ if (UTILS) ${CMAKE_CURRENT_LIST_DIR}/include ${MEMSTREAM_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/celix/gen/includes/utils + ${CMAKE_BINARY_DIR}/celix/gen/src/utils src include_deprecated ) target_link_libraries(utils_cut PUBLIC ${UTILS_PUBLIC_DEPS} ${UTILS_PRIVATE_DEPS}) diff --git a/libs/utils/gtest/src/CxxVersionTestSuite.cc b/libs/utils/gtest/src/CxxVersionTestSuite.cc index ac7266c3f..53276b247 100644 --- a/libs/utils/gtest/src/CxxVersionTestSuite.cc +++ b/libs/utils/gtest/src/CxxVersionTestSuite.cc @@ -42,14 +42,6 @@ TEST_F(CxxVersionTestSuite, ConstructorTest) { EXPECT_EQ(2, v1.getMinor()); EXPECT_EQ(3, v1.getMicro()); EXPECT_EQ("qualifier", v1.getQualifier()); - - #if __cplusplus >= 201703L //C++17 or higher - celix::Version v2{1, 2, 3, std::string_view{"qualifier"}}; - EXPECT_EQ(1, v2.getMajor()); - EXPECT_EQ(2, v2.getMinor()); - EXPECT_EQ(3, v2.getMicro()); - EXPECT_EQ("qualifier", v2.getQualifier()); - #endif } TEST_F(CxxVersionTestSuite, MoveConstructorTest) { diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 0b6f16439..1ae535f8d 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -25,6 +25,7 @@ #include "celix_err.h" #include "celix_properties.h" #include "celix_properties_private.h" +#include "celix_properties_constants.h" #include "celix_string_hash_map_ei.h" #include "celix_utils_ei.h" @@ -45,11 +46,20 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); } + /** + * Fills the optimization cache of the given properties object to ensure the next added entries will need + * allocation. + */ void fillOptimizationCache(celix_properties_t* props) { - for (int i = 0; i < 50; ++i) { + int index = 0; + size_t written = 0; + size_t nrOfEntries = 0; + while (written < CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE || + nrOfEntries++ < CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE) { const char* val = "1234567890"; char key[10]; - snprintf(key, sizeof(key), "key%i", i); + snprintf(key, sizeof(key), "key%i", index++); + written += strlen(key) + strlen(val) + 2; celix_properties_set(props, key, val); } } diff --git a/libs/utils/src/celix_properties_constants.h.in b/libs/utils/src/celix_properties_constants.h.in new file mode 100644 index 000000000..706d1d389 --- /dev/null +++ b/libs/utils/src/celix_properties_constants.h.in @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef CELIX_CELIX_PROPERTIES_CONSTANTS_H +#define CELIX_CELIX_PROPERTIES_CONSTANTS_H + +/** + * @brief The string optimization buffer size used in celix_properties so store some strings entries (keys and values) + * in a buffer instead of allocating memory for each entry. + */ +#define CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE @CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE@ + +/** +* @brief The entries optimization buffer size used in celix_properties so store some properties entries +* in a buffer instead of allocating memory for each entry. +*/ +#define CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE @CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE@ + +#endif //CELIX_CELIX_ERR_CONSTANTS_H diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 0c36d0f49..4643452f6 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -35,9 +35,7 @@ #include "celix_utils.h" #include "celix_stdlib_cleanup.h" #include "celix_convert_utils.h" - -#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 512 -#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16 +#include "celix_properties_constants.h" static const char* const CELIX_PROPERTIES_BOOL_TRUE_STRVAL = "true"; static const char* const CELIX_PROPERTIES_BOOL_FALSE_STRVAL = "false"; @@ -53,7 +51,7 @@ struct celix_properties { * @note based on some small testing most services properties seem to max out at 11 entries. * So 16 (next factor 2 based value) seems like a good fit. */ - char stringBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE]; + char stringBuffer[CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE]; /** * The current string buffer index. @@ -67,7 +65,7 @@ struct celix_properties { * @note based on some small testing most services properties seem to max around 300 bytes. * So 512 (next factor 2 based value) seems like a good fit. */ - celix_properties_entry_t entriesBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE]; + celix_properties_entry_t entriesBuffer[CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE]; /** * The current string buffer index. @@ -141,7 +139,7 @@ char* celix_properties_createString(celix_properties_t* properties, const char* return (char*)CELIX_PROPERTIES_EMPTY_STRVAL; } size_t len = strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; - size_t left = CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - properties->currentStringBufferIndex; + size_t left = CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - properties->currentStringBufferIndex; char* result; if (len < left) { memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, len); @@ -161,7 +159,7 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st str == CELIX_PROPERTIES_EMPTY_STRVAL) { // str is static const char* const -> nop } else if (str >= properties->stringBuffer && - str < (properties->stringBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { + str < (properties->stringBuffer + CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) { // str is part of the properties string buffer -> nop } else { free(str); @@ -231,7 +229,7 @@ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, */ celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties) { celix_properties_entry_t* entry; - if (properties->currentEntriesBufferIndex < CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) { + if (properties->currentEntriesBufferIndex < CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE) { entry = &properties->entriesBuffer[properties->currentEntriesBufferIndex++]; } else { entry = malloc(sizeof(*entry)); @@ -274,7 +272,7 @@ static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); if (status != CELIX_SUCCESS) { if (entry >= properties->entriesBuffer && - entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { + entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { // entry is part of the properties entries buffer -> nop. } else { free(entry); @@ -291,7 +289,7 @@ static void celix_properties_destroyEntry(celix_properties_t* properties, celix_ } if (entry >= properties->entriesBuffer && - entry <= (properties->entriesBuffer + CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) { + entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { // entry is part of the properties entries buffer -> nop. } else { free(entry); @@ -361,7 +359,7 @@ celix_properties_t* celix_properties_create() { if (props != NULL) { celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; opts.storeKeysWeakly = true; - opts.initialCapacity = (unsigned int)ceil(CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE / 0.75); + opts.initialCapacity = (unsigned int)ceil(CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE / 0.75); opts.removedCallbackData = props; opts.removedCallback = celix_properties_removeEntryCallback; opts.removedKeyCallback = celix_properties_removeKeyCallback; From e8c4072459a613deb2a061b96abe9d9d8131208d Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Thu, 9 Nov 2023 19:43:42 +0100 Subject: [PATCH 38/63] #509: Update properties handling in remote service zeroconf discovery --- .../discovery_zeroconf/src/discovery_zeroconf_announcer.c | 1 + 1 file changed, 1 insertion(+) diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c index 937b65c3e..d769b7f91 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c @@ -352,6 +352,7 @@ static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroc return false; } if (splitTxtRecord && TXTRecordGetLength(txtRecord) >= maxTxtLen - UINT8_MAX) { + celix_propertiesIterator_next(propIter); break; } } From 24eab9cb4e8b765d2089180f6e170ba000b4fc98 Mon Sep 17 00:00:00 2001 From: xuzhenbao <43104033+xuzhenbao@users.noreply.github.com> Date: Fri, 10 Nov 2023 12:23:18 +0800 Subject: [PATCH 39/63] Update discovery_zeroconf_announcer.c remove the `celix_propertiesIterator_next()`, `discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord` has do it --- .../discovery_zeroconf/src/discovery_zeroconf_announcer.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c index d769b7f91..f295c466a 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c @@ -425,7 +425,6 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno break; } TXTRecordDeallocate(&txtRecord); - celix_propertiesIterator_next(&propIter); } } } @@ -542,4 +541,4 @@ static void *discoveryZeroconfAnnouncer_refreshEndpointThread(void *data) { celix_arrayList_destroy(revokedEndpoints); celix_arrayList_destroy(announcedEndpoints); return NULL; -} \ No newline at end of file +} From ef6f343fa74c660dc2102fc99752f4c49048b613 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 11:37:37 +0100 Subject: [PATCH 40/63] #509: Improve hashmap/properties equals and apply properties equals in librcm --- libs/rcm/src/celix_capability.c | 32 ++------------------- libs/rcm/src/celix_requirement.c | 29 ++----------------- libs/utils/gtest/src/HashMapTestSuite.cc | 2 ++ libs/utils/gtest/src/PropertiesTestSuite.cc | 1 + libs/utils/src/celix_hash_map.c | 5 ++++ libs/utils/src/properties.c | 4 +++ 6 files changed, 18 insertions(+), 55 deletions(-) diff --git a/libs/rcm/src/celix_capability.c b/libs/rcm/src/celix_capability.c index 35be9a717..bcea63bcf 100644 --- a/libs/rcm/src/celix_capability.c +++ b/libs/rcm/src/celix_capability.c @@ -72,35 +72,9 @@ bool celix_capability_equals(const celix_capability_t* cap1, const celix_capabil return true; } - if (celix_properties_size(cap1->attributes) != celix_properties_size(cap2->attributes) || - celix_properties_size(cap1->directives) != celix_properties_size(cap2->directives)) { - return false; - } - - if (!celix_utils_stringEquals(cap1->ns, cap2->ns)) { - return false; - } - - //compare attributes - bool equals = true; - CELIX_PROPERTIES_ITERATE(cap1->attributes, visit) { - const char* value2 = celix_properties_get(cap2->attributes, visit.key, NULL); - if (!celix_utils_stringEquals(visit.entry.value, value2)) { - equals = false; - break; - } - } - if (!equals) { - return false; - } - CELIX_PROPERTIES_ITERATE(cap1->directives, visit) { - const char* value2 = celix_properties_get(cap2->directives, visit.key, NULL); - if (!celix_utils_stringEquals(visit.entry.value, value2)) { - equals = false; - break; - } - } - return equals; + return celix_utils_stringEquals(cap1->ns, cap2->ns) && + celix_properties_equals(cap1->attributes, cap2->attributes) && + celix_properties_equals(cap1->directives, cap2->directives); } unsigned int celix_capability_hashCode(const celix_capability_t* cap) { diff --git a/libs/rcm/src/celix_requirement.c b/libs/rcm/src/celix_requirement.c index a6480ca7c..d3ac3d9f7 100644 --- a/libs/rcm/src/celix_requirement.c +++ b/libs/rcm/src/celix_requirement.c @@ -79,32 +79,9 @@ bool celix_requirement_equals(const celix_requirement_t* req1, const celix_requi return true; } - if (celix_properties_size(req1->attributes) != celix_properties_size(req2->attributes) || - celix_properties_size(req1->directives) != celix_properties_size(req2->directives)) { - return false; - } - - if (!celix_utils_stringEquals(req1->ns, req2->ns)) { - return false; - } - - //compare attributes - CELIX_PROPERTIES_ITERATE(req1->attributes, visit) { - const char* val2 = celix_properties_get(req2->attributes, visit.key, NULL); - if (!celix_utils_stringEquals(visit.entry.value, val2)) { - return false; - } - } - - //compare directives - CELIX_PROPERTIES_ITERATE(req1->directives, visit) { - const char* val2 = celix_properties_get(req2->directives, visit.key, NULL); - if (!celix_utils_stringEquals(visit.entry.value, val2)) { - return false; - } - } - - return true; + return celix_utils_stringEquals(req1->ns, req2->ns) && + celix_properties_equals(req1->directives, req2->directives) && + celix_properties_equals(req1->attributes, req2->attributes); } unsigned int celix_requirement_hashCode(const celix_requirement_t* req) { diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 901495300..ffc2063a0 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -646,6 +646,7 @@ TEST_F(HashMapTestSuite, StringHashMapEqualsTest) { EXPECT_FALSE(celix_stringHashMap_equals(map1, nullptr)); EXPECT_FALSE(celix_stringHashMap_equals(nullptr, map2)); EXPECT_TRUE(celix_stringHashMap_equals(nullptr, nullptr)); + EXPECT_TRUE(celix_stringHashMap_equals(map1, map1)); celix_stringHashMap_putLong(map1, "key1", 42); celix_stringHashMap_putBool(map1, "key2", true); @@ -667,6 +668,7 @@ TEST_F(HashMapTestSuite, LongHashMapEqualsTest) { EXPECT_FALSE(celix_longHashMap_equals(map1, nullptr)); EXPECT_FALSE(celix_longHashMap_equals(nullptr, map2)); EXPECT_TRUE(celix_longHashMap_equals(nullptr, nullptr)); + EXPECT_TRUE(celix_longHashMap_equals(map1, map1)); celix_longHashMap_putLong(map1, 1, 42); celix_longHashMap_putBool(map1, 2, true); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 8edc242e8..1a0ae8258 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -624,6 +624,7 @@ TEST_F(PropertiesTestSuite, PropertiesEqualsTest) { celix_autoptr(celix_properties_t) prop1 = celix_properties_create(); EXPECT_FALSE(celix_properties_equals(prop1, nullptr)); + EXPECT_TRUE(celix_properties_equals(prop1, prop1)); celix_autoptr(celix_properties_t) prop2 = celix_properties_create(); EXPECT_TRUE(celix_properties_equals(prop1, prop2)); diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 7fe4f7124..ded28d007 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -592,9 +592,14 @@ void celix_longHashMap_clear(celix_long_hash_map_t* map) { } static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_map_t* map2) { + if (map1 == map2) { + return true; + } + if (map1->size != map2->size) { return false; } + for (celix_hash_map_entry_t* entry = celix_hashMap_firstEntry(map1); entry != NULL; entry = celix_hashMap_nextEntry(map1, entry)) { celix_hash_map_entry_t* entryMap2 = celix_hashMap_getEntry(map2, entry->key.strKey, entry->key.longKey); diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 4643452f6..cacac7de1 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -706,6 +706,7 @@ static bool celix_properties_entryEquals(const celix_properties_entry_t* entry1, if (entry1->valueType != entry2->valueType) { return false; } + switch (entry1->valueType) { case CELIX_PROPERTIES_VALUE_TYPE_LONG: return entry1->typed.longValue == entry2->typed.longValue; @@ -810,6 +811,9 @@ int celix_properties_size(const celix_properties_t* properties) { } bool celix_properties_equals(const celix_properties_t* props1, const celix_properties_t* props2) { + if (props1 == props2) { + return true; + } if (props1 == NULL && props2 == NULL) { return true; } From 9930ecb571fc145567ecdee882e53193b5e882d2 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 11:50:26 +0100 Subject: [PATCH 41/63] #509: Add Properties equal operator --- libs/utils/gtest/src/CxxPropertiesTestSuite.cc | 6 +++--- libs/utils/include/celix/Properties.h | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index 214d77010..f041a187f 100644 --- a/libs/utils/gtest/src/CxxPropertiesTestSuite.cc +++ b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc @@ -189,14 +189,14 @@ TEST_F(CxxPropertiesTestSuite, StoreAndLoadTest) { std::string path{"cxx_store_and_load_test.properties"}; celix::Properties props{}; - props.set("key1", 1); - props.set("key2", 2); + props.set("key1", "1"); + props.set("key2", "2"); EXPECT_NO_THROW(props.store(path)); celix::Properties loadedProps{}; EXPECT_NO_THROW(loadedProps = celix::Properties::load(path)); - EXPECT_EQ(props.size(), loadedProps.size()); + EXPECT_TRUE(loadedProps == props); try { loadedProps = celix::Properties::load("non-existence"); diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 6037d98e1..163b83ca9 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -247,6 +247,15 @@ namespace celix { return ValueRef{cProps, std::move(key)}; } + /** + * @brief Compare two properties objects for equality. + * @param rhs + * @return true if the properties are equal, false otherwise. + */ + bool operator==(const Properties& rhs) const { + return celix_properties_equals(cProps.get(), rhs.cProps.get()); + } + /** * @brief begin iterator */ From f1beb5f9a150d7d6ed47c7dc3ce7b89bea6b6852 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 11:54:16 +0100 Subject: [PATCH 42/63] #509: Fix memleak in celix_properties_setWithoutCopy --- .../gtest/src/PropertiesErrorInjectionTestSuite.cc | 12 ++++++++++++ libs/utils/src/properties.c | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 1ae535f8d..7deaa39bc 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -241,6 +241,18 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetWithoutCopyFailureTest) { // And a celix err msg is set ASSERT_EQ(1, celix_err_getErrorCount()); celix_err_resetErrors(); + + //Given an allocated key and valu + key = celix_utils_strdup("key"); + val = celix_utils_strdup("value"); + // When a celix_stringHashMap_put error injection is set for celix_properties_setWithoutCopy + celix_ei_expect_celix_stringHashMap_put((void*)celix_properties_setWithoutCopy, 0, CELIX_ENOMEM); + // Then the celix_properties_setWithoutCopy call fails + status = celix_properties_setWithoutCopy(props, key, val); + ASSERT_EQ(status, CELIX_ENOMEM); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); } TEST_F(PropertiesErrorInjectionTestSuite, LoadFromStringFailureTest) { diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index cacac7de1..0d987bb27 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -674,7 +674,11 @@ celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, c bool alreadyExist = celix_stringHashMap_hasKey(properties->map, key); celix_status_t status = celix_stringHashMap_put(properties->map, key, entry); - if (status == CELIX_SUCCESS && alreadyExist) { + if (status != CELIX_SUCCESS) { + celix_err_pushf("Failed to put entry for key %s in map.", key); + free(key); + celix_properties_destroyEntry(properties, entry); + } else if (alreadyExist) { free(key); } return status; From cb0a8ba933113f2098f69bbb1ffb79b330175b3c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 14:10:52 +0100 Subject: [PATCH 43/63] #509: Improve some error handling in celix properties. Also: - Add some clarification of when error messaged are pushed to celix err in the celix properties header documentation. --- .../src/PropertiesErrorInjectionTestSuite.cc | 22 ++++++++- libs/utils/gtest/src/PropertiesTestSuite.cc | 34 ++++++++++++++ libs/utils/include/celix_properties.h | 29 +++++++++++- libs/utils/src/properties.c | 45 ++++++++++++++----- 4 files changed, 117 insertions(+), 13 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 7deaa39bc..3172a8c23 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -37,13 +37,15 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: PropertiesErrorInjectionTestSuite() = default; ~PropertiesErrorInjectionTestSuite() override { + celix_err_resetErrors(); celix_ei_expect_malloc(nullptr, 0, nullptr); - celix_ei_expect_celix_stringHashMap_create(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_createWithOptions(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); celix_ei_expect_fopen(nullptr, 0, nullptr); celix_ei_expect_fputc(nullptr, 0, 0); celix_ei_expect_fseek(nullptr, 0, 0); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0); } /** @@ -77,10 +79,26 @@ TEST_F(PropertiesErrorInjectionTestSuite, CreateFailureTest) { TEST_F(PropertiesErrorInjectionTestSuite, CopyFailureTest) { // C API + + //Given a celix properties object with more entries than the optimization cache celix_autoptr(celix_properties_t) prop = celix_properties_create(); ASSERT_NE(nullptr, prop); + fillOptimizationCache(prop); + celix_properties_set(prop, "additionalKey", "value"); + + // When a hash map create error injection is set for celix_properties_create celix_ei_expect_celix_stringHashMap_createWithOptions((void*)celix_properties_create, 0, nullptr); + // Then the celix_properties_copy call fails ASSERT_EQ(nullptr, celix_properties_copy(prop)); + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + + // When a malloc error injection is set for celix_properties_allocEntry (during set) + celix_ei_expect_malloc((void*)celix_properties_allocEntry, 0, nullptr); + // Then the celix_properties_copy call fails + ASSERT_EQ(nullptr, celix_properties_copy(prop)); + ASSERT_GE(celix_err_getErrorCount(), 1); + celix_err_resetErrors(); // C++ API const celix::Properties cxxProp{}; @@ -94,7 +112,7 @@ TEST_F(PropertiesErrorInjectionTestSuite, SetFailureTest) { celix_autoptr(celix_properties_t) props = celix_properties_create(); fillOptimizationCache(props); - // When a malloc error injection is set for celix_properties_set (during alloc entry) + // When a malloc error injection is set for celix_properties_allocEntry (during set) celix_ei_expect_malloc((void*)celix_properties_allocEntry, 0, nullptr); // Then the celix_properties_set call fails ASSERT_EQ(celix_properties_set(props, "key", "value"), CELIX_ENOMEM); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 1a0ae8258..675b90864 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -22,11 +22,15 @@ #include "celix_properties.h" #include "celix_utils.h" #include "properties.h" +#include "celix_err.h" using ::testing::MatchesRegex; class PropertiesTestSuite : public ::testing::Test { public: + PropertiesTestSuite() { + celix_err_resetErrors(); + } }; @@ -649,3 +653,33 @@ TEST_F(PropertiesTestSuite, PropertiesEqualsTest) { celix_properties_setDouble(prop2, "key5", 42.0); EXPECT_FALSE(celix_properties_equals(prop1, prop2)); //different types } + +TEST_F(PropertiesTestSuite, PropertiesNullArgumentsTest) { + //Silently ignore nullptr properties arguments for set* and copy functions + EXPECT_EQ(CELIX_SUCCESS, celix_properties_set(nullptr, "key", "value")); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setLong(nullptr, "key", 1)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setDouble(nullptr, "key", 1.0)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setBool(nullptr, "key", true)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setVersion(nullptr, "key", nullptr)); + celix_autoptr(celix_properties_t) copy = celix_properties_copy(nullptr); + EXPECT_NE(nullptr, copy); +} + +TEST_F(PropertiesTestSuite, InvalidArgumentsTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + celix_autoptr(celix_version_t) version = celix_version_create(1,2,3, nullptr); + + //Key cannot be nullptr and set functions should fail + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_set(props, nullptr, "value")); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setLong(props, nullptr, 1)); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setDouble(props, nullptr, 1.0)); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setBool(props, nullptr, true)); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setVersion(props, nullptr, version)); + EXPECT_EQ(5, celix_err_getErrorCount()); + + celix_err_resetErrors(); + //Set without copy should fail if a key or value is nullptr + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setWithoutCopy(props, nullptr, strdup("value"))); + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setWithoutCopy(props, strdup("key"), nullptr)); + EXPECT_EQ(2, celix_err_getErrorCount()); +} diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index f106b081f..edc5a6848 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -117,6 +117,8 @@ typedef struct celix_properties_iterator { /** * @brief Create a new empty property set. * + * This function will push a error msg to celix_err if the return value is NULL. + * * @return A new empty property set. */ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(); @@ -133,6 +135,8 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_properties_t, celix_properties_destroy) /** * @brief Load properties from a file. * + * This function will push a error msg to celix_err if the return value is NULL. + * * @param[in] filename The name of the file to load properties from. * @return A property set containing the properties from the file. * @retval NULL If an error occurred (e.g. file not found). @@ -142,6 +146,8 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filenam /** * @brief Load properties from a stream. * + * This function will push a error msg to celix_err if the return value is NULL. + * * @param[in,out] stream The stream to load properties from. * @return A property set containing the properties from the stream. * @retval NULL If an error occurred (e.g. invalid format). @@ -151,6 +157,8 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* str /** * @brief Load properties from a string. * + * This function will push a error msg to celix_err if the return value is NULL. + * * @param[in] input The string to load properties from. * @return A property set containing the properties from the string. * @retval NULL If an error occurred (e.g. invalid format). @@ -162,6 +170,8 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const cha * * @note Properties values are always stored as string values, regardless of their actual underlining types. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. * @param[in] header An optional - single line - header to write to the file before the properties. @@ -207,6 +217,8 @@ CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const /** * @brief Set the value of a property. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. @@ -219,6 +231,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* prope /** * @brief Set the value of a property without copying the value string. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or * modified after calling this function. @@ -233,6 +247,9 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properti /** * @brief Set the value of a property based on the provided property entry, maintaining underlying type. + * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling @@ -252,7 +269,9 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char* key); /** - * @brief Make a copy of a property set. + * @brief Make a copy of a properties set. + * + * This function will push a error msg to celix_err if the return value is NULL. * * @param[in] properties The property set to copy. * @return A copy of the given property set. @@ -276,6 +295,8 @@ celix_properties_getAsLong(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a long integer. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The long value to set the property to. @@ -300,6 +321,8 @@ celix_properties_getAsBool(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a boolean. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The boolean value to set the property to. @@ -310,6 +333,8 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setBool(celix_properties_t* p /** * @brief Set the value of a property to a double. * + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The double value to set the property to. @@ -337,6 +362,7 @@ celix_properties_getAsDouble(const celix_properties_t* properties, const char* k * @brief Set the value of a property as a Celix version. * * This function will make a copy of the provided celix_version_t object and store it in the property set. + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -352,6 +378,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t * * This function will store a reference to the provided celix_version_t object in the property set and takes * ownership of the provided version. + * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 0d987bb27..c5381922b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -265,12 +265,16 @@ static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t celix_version_t* versionValue) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { + celix_err_pushf("Cannot allocate property entry"); + celix_version_destroy(versionValue); return NULL; } celix_status_t status = celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); if (status != CELIX_SUCCESS) { + celix_err_pushf("Cannot fill property entry"); + celix_version_destroy(versionValue); if (entry >= properties->entriesBuffer && entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { // entry is part of the properties entries buffer -> nop. @@ -308,11 +312,14 @@ static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* pro const bool* boolValue, celix_version_t* versionValue) { if (!properties) { + celix_version_destroy(versionValue); return CELIX_SUCCESS; // silently ignore } + if (!key) { - celix_err_push("Cannot set property with NULL key"); - return CELIX_SUCCESS; // print error and ignore + celix_version_destroy(versionValue); + celix_err_pushf("Cannot set property with NULL key"); + return CELIX_ILLEGAL_ARGUMENT; } celix_properties_entry_t* entry = @@ -616,25 +623,37 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char celix_properties_t* celix_properties_copy(const celix_properties_t* properties) { celix_properties_t* copy = celix_properties_create(); - if (properties == NULL) { + + if (!copy) { + celix_err_push("Failed to create properties copy"); + return NULL; + } + + if (!properties) { return copy; } CELIX_PROPERTIES_ITERATE(properties, iter) { + celix_status_t status; if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { - celix_properties_set(copy, iter.key, iter.entry.value); + status = celix_properties_set(copy, iter.key, iter.entry.value); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { - celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue); + status = celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { - celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue); + status = celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue); } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { - celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue); + status = celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue); } else /*version*/ { assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION); - celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue); + status = celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue); + } + if (status != CELIX_SUCCESS) { + celix_err_pushf("Failed to copy property %s", iter.key); + celix_properties_destroy(copy); + return NULL; } } - return copy; + return celix_steal_ptr(copy); } celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key) { @@ -663,7 +682,13 @@ celix_status_t celix_properties_set(celix_properties_t* properties, const char* } celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) { - if (properties != NULL && key != NULL && value != NULL) { + if (properties) { + if (!key || !value) { + celix_err_push("Failed to set (without copy) property. Key or value is NULL."); + free(key); + free(value); + return CELIX_ILLEGAL_ARGUMENT; + } celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value); if (!entry) { celix_err_push("Failed to create entry for property."); From be9f0a9dd5cbda156ad8e03043b94a5249a52e85 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 15:30:32 +0100 Subject: [PATCH 44/63] Improve some header doc for string hash map / properties Also add a missing celix_status_t return for celix_properties_setVersionWithoutCopy --- libs/utils/gtest/src/PropertiesTestSuite.cc | 10 +- libs/utils/include/celix_properties.h | 120 +++++++++++--------- libs/utils/include/celix_string_hash_map.h | 13 ++- libs/utils/src/properties.c | 4 +- 4 files changed, 78 insertions(+), 69 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 675b90864..75cdc5fde 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -311,15 +311,15 @@ TEST_F(PropertiesTestSuite, GetSetOverwrite) { free(key); } EXPECT_STREQ("str1", celix_properties_get(props, "key", "")); - celix_properties_set(props, "key", "str2"); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_set(props, "key", "str2")); EXPECT_STREQ("str2", celix_properties_get(props, "key", "")); - celix_properties_setLong(props, "key", 1); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setLong(props, "key", 1)); EXPECT_EQ(1, celix_properties_getAsLong(props, "key", -1L)); - celix_properties_setDouble(props, "key", 2.0); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setDouble(props, "key", 2.0)); EXPECT_EQ(2.0, celix_properties_getAsLong(props, "key", -2.0)); - celix_properties_setBool(props, "key", false); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setBool(props, "key", false)); EXPECT_EQ(false, celix_properties_getAsBool(props, "key", true)); - celix_properties_setVersionWithoutCopy(props, "key", version); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setVersionWithoutCopy(props, "key", version)); EXPECT_EQ(version, celix_properties_getVersion(props, "key", nullptr)); celix_properties_set(props, "key", "last"); diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index edc5a6848..48dd6b801 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -117,7 +117,7 @@ typedef struct celix_properties_iterator { /** * @brief Create a new empty property set. * - * This function will push a error msg to celix_err if the return value is NULL. + * If the return status is an error, a error message is logged to celix_err. * * @return A new empty property set. */ @@ -135,7 +135,7 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_properties_t, celix_properties_destroy) /** * @brief Load properties from a file. * - * This function will push a error msg to celix_err if the return value is NULL. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] filename The name of the file to load properties from. * @return A property set containing the properties from the file. @@ -146,7 +146,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filenam /** * @brief Load properties from a stream. * - * This function will push a error msg to celix_err if the return value is NULL. + * If the return status is an error, a error message is logged to celix_err. * * @param[in,out] stream The stream to load properties from. * @return A property set containing the properties from the stream. @@ -157,7 +157,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* str /** * @brief Load properties from a string. * - * This function will push a error msg to celix_err if the return value is NULL. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] input The string to load properties from. * @return A property set containing the properties from the string. @@ -170,7 +170,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const cha * * @note Properties values are always stored as string values, regardless of their actual underlining types. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. @@ -217,12 +217,13 @@ CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const /** * @brief Set the value of a property. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The value to set the property to. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, @@ -231,53 +232,21 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* prope /** * @brief Set the value of a property without copying the value string. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. - * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or -* modified after calling this function. - * @param[in] value The value to set the property to. This string will be used directly, so it must not be freed or - * modified after calling this function. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @param[in] key The key of the property to set. This callee will take ownership of the key, so the key must not be + * used after calling this function. The key should be deallocated with free. + * @param[in] value The value to set the property to. This callee will take ownership of the value, so the value must + * not be used after calling this function. The value should be deallocated with free. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key or value is NULL. * When CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value); -/** - * @brief Set the value of a property based on the provided property entry, maintaining underlying type. - * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. - * - * @param[in] properties The property set to modify. - * @param[in] key The key of the property to set. - * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling - * this function. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. - */ -CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* properties, - const char* key, - const celix_properties_entry_t* entry); - -/** - * @brief Unset a property, removing it from the property set. - * - * @param[in] properties The property set to modify. - * @param[in] key The key of the property to unset. - */ -CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char* key); - -/** - * @brief Make a copy of a properties set. - * - * This function will push a error msg to celix_err if the return value is NULL. - * - * @param[in] properties The property set to copy. - * @return A copy of the given property set. - */ -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t* properties); - /** * @brief Get the value of a property as a long integer. * @@ -295,12 +264,13 @@ celix_properties_getAsLong(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a long integer. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] value The long value to set the property to. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setLong(celix_properties_t* properties, const char* key, long value); @@ -321,24 +291,26 @@ celix_properties_getAsBool(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a boolean. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The boolean value to set the property to. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setBool(celix_properties_t* properties, const char* key, bool val); /** * @brief Set the value of a property to a double. * - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] val The double value to set the property to. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setDouble(celix_properties_t* properties, const char* key, @@ -362,12 +334,13 @@ celix_properties_getAsDouble(const celix_properties_t* properties, const char* k * @brief Set the value of a property as a Celix version. * * This function will make a copy of the provided celix_version_t object and store it in the property set. - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] version The value to set. The function will make a copy of this object and store it in the property set. - * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t* properties, const char* key, @@ -378,14 +351,15 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t * * This function will store a reference to the provided celix_version_t object in the property set and takes * ownership of the provided version. - * This function will push a error msg to celix_err if the return code is not CELIX_SUCCESS. + * If the return status is an error, a error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. * @param[in] version The value to set. The function will store a reference to this object in the property set and * takes ownership of the provided version. + * */ -CELIX_UTILS_EXPORT void +CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); /** @@ -423,6 +397,40 @@ CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion(const celix_pr const char* key, const celix_version_t* defaultValue); +/** + * @brief Set the value of a property based on the provided property entry, maintaining underlying type. + * + * If the return status is an error, a error message is logged to celix_err. + * + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to set. + * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling + * this function. + * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. + */ +CELIX_UTILS_EXPORT celix_status_t celix_properties_setEntry(celix_properties_t* properties, + const char* key, + const celix_properties_entry_t* entry); + +/** + * @brief Unset a property, removing it from the property set. + * + * @param[in] properties The property set to modify. + * @param[in] key The key of the property to unset. + */ +CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, const char* key); + +/** + * @brief Make a copy of a properties set. + * + * If the return status is an error, a error message is logged to celix_err. + * + * @param[in] properties The property set to copy. + * @return A copy of the given property set. + */ +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t* properties); + /** * @brief Get the number of properties in a property set. * diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 72fb869f7..b0e8c6ff3 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -117,12 +117,13 @@ typedef struct celix_string_hash_map_create_options { * @brief If set to true, the string hash map will not make of copy of the keys and assumes * that the keys are in scope/memory for the complete lifecycle of the string hash map. * - * When keys are stored weakly it is the caller responsibility to check the return value of - * celix_stringHashMap_put* function calls. - * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key - * should never be freed or freed in a configured removedKeyCallback. - * If a celix_stringHashMap_put* function call returns false, a value is replaced and the already existing - * key is reused. If the needed the caller should free the provided key. + * When keys are stored weakly it is the caller responsibility to check if a key is already in use + * (celix_stringHashMap_hasKey). + * + * If the key is already in use, the celix_stringHashMap_put* will not store the provided key and will + * instead keep the already existing key. If needed, the caller should free the provided key. + * If the key is not already in use, the celix_stringHashMap_put* will store the provided key and the caller + * should not free the provided key. * * @note This changes the default behaviour of the celix_stringHashMap_put* functions. * diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index c5381922b..542e1d2b2 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -831,8 +831,8 @@ celix_properties_setVersion(celix_properties_t* properties, const char* key, con return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); } -void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) { - celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version); +celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) { + return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version); } int celix_properties_size(const celix_properties_t* properties) { From 9d0ad5779240bcc0328702f5cce2ed2af5f86293 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 16:17:48 +0100 Subject: [PATCH 45/63] Add some error handling for version Also: - Remove dup include in properties.c - Remove some dead code in celix_hash --- libs/utils/gtest/CMakeLists.txt | 1 + .../src/HashMapErrorInjectionTestSuite.cc | 5 +++- .../src/VersionErrorInjectionTestSuite.cc | 25 ++++++++++++----- libs/utils/include/celix_properties.h | 6 ++--- libs/utils/include/celix_version.h | 8 ++---- libs/utils/src/celix_hash_map.c | 2 -- libs/utils/src/version.c | 27 ++++++++++++++----- 7 files changed, 49 insertions(+), 25 deletions(-) diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 4a340d555..784e8742e 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -110,6 +110,7 @@ if (EI_TESTS) Celix::ifaddrs_ei Celix::threads_ei Celix::malloc_ei + Celix::asprintf_ei Celix::string_hash_map_ei Celix::long_hash_map_ei GTest::gtest GTest::gtest_main diff --git a/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc index 956d01497..198a21863 100644 --- a/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc @@ -30,7 +30,10 @@ class HashMapErrorInjectionTestSuite : public ::testing::Test { public: - HashMapErrorInjectionTestSuite() { celix_ei_expect_calloc(nullptr, 0, nullptr); } + HashMapErrorInjectionTestSuite() { + celix_ei_expect_calloc(nullptr, 0, nullptr); + celix_err_resetErrors(); + } }; TEST_F(HashMapErrorInjectionTestSuite, CreateFailureTest) { diff --git a/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc index cf570c6ca..019c87263 100644 --- a/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc @@ -20,8 +20,10 @@ #include #include "celix_utils_ei.h" +#include "asprintf_ei.h" #include "malloc_ei.h" #include "celix_version.h" +#include "celix_err.h" class VersionErrorInjectionTestSuite : public ::testing::Test { @@ -29,19 +31,28 @@ class VersionErrorInjectionTestSuite : public ::testing::Test { VersionErrorInjectionTestSuite() { celix_ei_expect_calloc(nullptr, 0, nullptr); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); - } - ~VersionErrorInjectionTestSuite() noexcept override { - celix_ei_expect_calloc(nullptr, 0, nullptr); - celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); + celix_ei_expect_asprintf(nullptr, 0, 0); + celix_err_resetErrors(); } }; -TEST_F(VersionErrorInjectionTestSuite, CreateTest) { - celix_ei_expect_calloc(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); +TEST_F(VersionErrorInjectionTestSuite, CreateFailureTest) { + celix_ei_expect_calloc((void*)celix_version_create, 0, nullptr); celix_version_t *version = celix_version_create(2, 2, 0, nullptr); EXPECT_EQ(nullptr, version); - celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); + celix_ei_expect_celix_utils_strdup((void*)celix_version_create, 0, nullptr); version = celix_version_create(2, 2, 0, "qualifier"); EXPECT_EQ(nullptr, version); + + EXPECT_EQ(2, celix_err_getErrorCount()); } + +TEST_F(VersionErrorInjectionTestSuite, ToStringFailureTest) { + celix_autoptr(celix_version_t) version = celix_version_create(2, 2, 0, "qualifier"); + + celix_ei_expect_asprintf((void*)celix_version_toString, 0, -1); + char *str = celix_version_toString(version); + EXPECT_EQ(nullptr, str); + EXPECT_EQ(1, celix_err_getErrorCount()); +} \ No newline at end of file diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 48dd6b801..caade747c 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -35,6 +35,9 @@ * - celix_version_t* */ +#ifndef CELIX_PROPERTIES_H_ +#define CELIX_PROPERTIES_H_ + #include #include "celix_cleanup.h" @@ -43,9 +46,6 @@ #include "celix_utils_export.h" #include "celix_version.h" -#ifndef CELIX_PROPERTIES_H_ -#define CELIX_PROPERTIES_H_ - #ifdef __cplusplus extern "C" { #endif diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index 8b713ee1e..03cf0428d 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -23,17 +23,13 @@ #include #include +#include "celix_cleanup.h" #include "celix_utils_export.h" #ifdef __cplusplus extern "C" { #endif -#include - -#include "celix_cleanup.h" -#include "celix_utils_export.h" - /** * @file celix_version.h * @brief Header file for the Celix Version API. @@ -131,7 +127,7 @@ CELIX_UTILS_EXPORT int celix_version_getMicro(const celix_version_t* version); * @brief Gets the version qualifier of a celix version. * * @param[in] version The celix version. - * @return The version qualifier, or NULL if no qualifier is present. + * @return The version qualifier, or an empty string ("") if no qualifier is present. */ CELIX_UTILS_EXPORT const char* celix_version_getQualifier(const celix_version_t* version); diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index ded28d007..fa92ceb04 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -56,7 +56,6 @@ struct celix_hash_map { unsigned int size; //nr of total entries double loadFactor; celix_hash_map_key_type_e keyType; - celix_hash_map_value_t emptyValue; unsigned int (*hashKeyFunction)(const celix_hash_map_key_t* key); bool (*equalsKeyFunction)(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2); void (*simpleRemovedCallback)(void* value); @@ -351,7 +350,6 @@ celix_status_t celix_hashMap_init( map->size = 0; map->bucketsSize = initialCapacity; map->keyType = keyType; - memset(&map->emptyValue, 0, sizeof(map->emptyValue)); map->hashKeyFunction = hashKeyFn; map->equalsKeyFunction = equalsKeyFn; map->simpleRemovedCallback = NULL; diff --git a/libs/utils/src/version.c b/libs/utils/src/version.c index 2c1d01fc8..07652a007 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -26,6 +26,7 @@ #include "version.h" #include "celix_errno.h" #include "version_private.h" +#include "celix_err.h" static const char* const CELIX_VERSION_EMPTY_QUALIFIER = ""; @@ -99,12 +100,14 @@ celix_status_t version_isCompatible(version_pt user, version_pt provider, bool* celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier) { if (major < 0 || minor < 0 || micro < 0) { + celix_err_push("Invalid version number. Major, minor and micro must be >= 0"); return NULL; } if (qualifier == NULL) { qualifier = CELIX_VERSION_EMPTY_QUALIFIER; } + size_t qualifierLen = strlen(qualifier); for (int i = 0; i < qualifierLen; i++) { char ch = qualifier[i]; @@ -120,21 +123,28 @@ celix_version_t* celix_version_create(int major, int minor, int micro, const cha if ((ch == '_') || (ch == '-')) { continue; } - //invalid + celix_err_push("Invalid version qualifier. Characters must be [A-Za-z0-9_-]"); return NULL; } celix_version_t* version = calloc(1, sizeof(*version)); - if (version != NULL) { + if (version) { version->major = major; version->minor = minor; version->micro = micro; - version->qualifier = qualifierLen == 0 ? (char*)CELIX_VERSION_EMPTY_QUALIFIER : celix_utils_strdup(qualifier); - if (version->qualifier == NULL) { + if (qualifierLen == 0) { + version->qualifier = (char*)CELIX_VERSION_EMPTY_QUALIFIER; + } else { + version->qualifier = celix_utils_strdup(qualifier); + } + if (!version->qualifier) { celix_version_destroy(version); version = NULL; } } + if (!version) { + celix_err_push("Failed to allocate memory for celix_version_create"); + } return version; } @@ -289,10 +299,15 @@ int celix_version_compareTo(const celix_version_t* version, const celix_version_ char* celix_version_toString(const celix_version_t* version) { char* string = NULL; + int rc; if (strlen(version->qualifier) > 0) { - asprintf(&string,"%d.%d.%d.%s", version->major, version->minor, version->micro, version->qualifier); + rc = asprintf(&string,"%d.%d.%d.%s", version->major, version->minor, version->micro, version->qualifier); } else { - asprintf(&string, "%d.%d.%d", version->major, version->minor, version->micro); + rc = asprintf(&string, "%d.%d.%d", version->major, version->minor, version->micro); + } + if (rc < 0) { + celix_err_push("Failed to allocate memory for celix_version_toString"); + return NULL; } return string; } From 4bb275c989d139a1791c3fc33b31ce149fe20d31 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 10 Nov 2023 16:51:04 +0100 Subject: [PATCH 46/63] Refactor celix_properties_constants.h to celix_utils_private_constants.h Also add CELIX_UTILS_MAX_STRLEN as build configurable option. --- .../src/pubsub_wire_protocol_common.c | 5 +---- conanfile.py | 3 +++ libs/utils/CMakeLists.txt | 5 +++-- libs/utils/gtest/src/CelixUtilsTestSuite.cc | 10 ++++++++++ .../gtest/src/PropertiesErrorInjectionTestSuite.cc | 2 +- libs/utils/include/celix_utils.h | 9 +++++++-- ...ants.h.in => celix_utils_private_constants.h.in} | 13 ++++++++++--- libs/utils/src/properties.c | 2 +- libs/utils/src/utils.c | 11 ++++++----- 9 files changed, 42 insertions(+), 18 deletions(-) rename libs/utils/src/{celix_properties_constants.h.in => celix_utils_private_constants.h.in} (81%) diff --git a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c index 88a9bbff5..ef17ed0a8 100644 --- a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c +++ b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c @@ -39,10 +39,7 @@ static celix_status_t pubsubProtocol_addNetstringEntryToBuffer(char* buffer, size_t bufferSize, size_t* offsetInOut, const char* str) { size_t offset = *offsetInOut; - size_t strLen = strnlen(str, CELIX_UTILS_MAX_STRLEN); - if (strLen == CELIX_UTILS_MAX_STRLEN) { - return CELIX_ILLEGAL_ARGUMENT; - } + size_t strLen = celix_utils_strlen(str); char strLenString[32]; //note the str needed to print the strLen of str. int written = snprintf(strLenString, sizeof(strLenString), "%zu", strLen); diff --git a/conanfile.py b/conanfile.py index 877ab7fd4..f8288bca6 100644 --- a/conanfile.py +++ b/conanfile.py @@ -102,11 +102,13 @@ class CelixConan(ConanFile): } options = { "celix_err_buffer_size": ["ANY"], + "celix_utils_max_strlen": ["ANY"], "celix_properties_optimization_string_buffer_size": ["ANY"], "celix_properties_optimization_entries_buffer_size": ["ANY"], } default_options = { "celix_err_buffer_size": "512", + "celix_utils_max_strlen": "1073741824", "celix_properties_optimization_string_buffer_size": "128", "celix_properties_optimization_entries_buffer_size": "16", } @@ -136,6 +138,7 @@ def validate(self): raise ConanInvalidConfiguration("Celix build_rsa_discovery_zeroconf is only supported for Linux") self.validate_config_option_is_positive_number("celix_err_buffer_size") + self.validate_config_option_is_positive_number("celix_utils_max_strlen") self.validate_config_option_is_positive_number("celix_properties_optimization_string_buffer_size") self.validate_config_option_is_positive_number("celix_properties_optimization_entries_buffer_size") diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index f937ab5d6..2738a148e 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -97,10 +97,11 @@ if (UTILS) set(CELIX_ERR_BUFFER_SIZE 512 CACHE STRING "The size of the thread-specific buffer used for library error messages") configure_file("${CMAKE_CURRENT_LIST_DIR}/src/celix_err_constants.h.in" "${CMAKE_BINARY_DIR}/celix/gen/includes/utils/celix_err_constants.h" @ONLY) - #Configure celix_properties_constant (private) header file + #Configure celix_utils_private_constants header file + set(CELIX_UTILS_MAX_STRLEN 1073741824 CACHE STRING "The maximum string length used for string util functions") set(CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 128 CACHE STRING "The string optimization buffer size used for properties") set(CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE 16 CACHE STRING "The entries optimization buffer size used for properties") - configure_file("${CMAKE_CURRENT_LIST_DIR}/src/celix_properties_constants.h.in" "${CMAKE_BINARY_DIR}/celix/gen/src/utils/celix_properties_constants.h" @ONLY) + configure_file("${CMAKE_CURRENT_LIST_DIR}/src/celix_utils_private_constants.h.in" "${CMAKE_BINARY_DIR}/celix/gen/src/utils/celix_utils_private_constants.h" @ONLY) install(TARGETS utils EXPORT celix LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT framework INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/celix/utils) diff --git a/libs/utils/gtest/src/CelixUtilsTestSuite.cc b/libs/utils/gtest/src/CelixUtilsTestSuite.cc index 6d287550b..26bef64b8 100644 --- a/libs/utils/gtest/src/CelixUtilsTestSuite.cc +++ b/libs/utils/gtest/src/CelixUtilsTestSuite.cc @@ -19,6 +19,7 @@ #include +#include "celix_stdlib_cleanup.h" #include "celix_utils.h" #include "utils.h" @@ -314,4 +315,13 @@ TEST_F(UtilsTestSuite, WriteOrCreateStringTest) { celix_utils_freeStringIfNotEqual(buffer2, out2); } +TEST_F(UtilsTestSuite, StrDupAndStrLenTest) { + celix_autofree char* str = celix_utils_strdup("abc"); + ASSERT_NE(nullptr, str); + EXPECT_STREQ("abc", str); + size_t len = celix_utils_strlen(str); + EXPECT_EQ(3, len); + + EXPECT_EQ(nullptr, celix_utils_strdup(nullptr)); +} diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 3172a8c23..ea507e2dd 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -25,7 +25,7 @@ #include "celix_err.h" #include "celix_properties.h" #include "celix_properties_private.h" -#include "celix_properties_constants.h" +#include "celix_utils_private_constants.h" #include "celix_string_hash_map_ei.h" #include "celix_utils_ei.h" diff --git a/libs/utils/include/celix_utils.h b/libs/utils/include/celix_utils.h index 30168409e..7933b4ebe 100644 --- a/libs/utils/include/celix_utils.h +++ b/libs/utils/include/celix_utils.h @@ -30,11 +30,9 @@ extern "C" { #include "celix_utils_export.h" -#define CELIX_UTILS_MAX_STRLEN 1024*1024*1024 #define CELIX_US_IN_SEC (1000000) #define CELIX_NS_IN_SEC ((CELIX_US_IN_SEC)*1000) - /** * Creates a copy of a provided string. * The strdup is limited to the CELIX_UTILS_MAX_STRLEN and uses strndup to achieve this. @@ -42,6 +40,13 @@ extern "C" { */ CELIX_UTILS_EXPORT char* celix_utils_strdup(const char *str); +/** + * Returns the length of the provided string with a max of CELIX_UTILS_MAX_STRLEN. + * @param str The string to get the length from. + * @return The length of the string or 0 if the string is NULL. + */ +CELIX_UTILS_EXPORT size_t celix_utils_strlen(const char *str); + /** * @brief Creates a hash from a string * @param string diff --git a/libs/utils/src/celix_properties_constants.h.in b/libs/utils/src/celix_utils_private_constants.h.in similarity index 81% rename from libs/utils/src/celix_properties_constants.h.in rename to libs/utils/src/celix_utils_private_constants.h.in index 706d1d389..eadb684db 100644 --- a/libs/utils/src/celix_properties_constants.h.in +++ b/libs/utils/src/celix_utils_private_constants.h.in @@ -17,8 +17,15 @@ * under the License. */ -#ifndef CELIX_CELIX_PROPERTIES_CONSTANTS_H -#define CELIX_CELIX_PROPERTIES_CONSTANTS_H +#ifndef CELIX_UTILS_PRIVATE_CONSTANTS_H +#define CELIX_UTILS_PRIVATE_CONSTANTS_H + + +/** + * @brief The max string length used in celix_utils for Apache Celix variant functions for strlen, strcmp and + * strdup. + */ +#define CELIX_UTILS_MAX_STRLEN @CELIX_UTILS_MAX_STRLEN@ /** * @brief The string optimization buffer size used in celix_properties so store some strings entries (keys and values) @@ -32,4 +39,4 @@ */ #define CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE @CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE@ -#endif //CELIX_CELIX_ERR_CONSTANTS_H +#endif //CELIX_UTILS_PRIVATE_CONSTANTS_H diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 542e1d2b2..cfbd63794 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -35,7 +35,7 @@ #include "celix_utils.h" #include "celix_stdlib_cleanup.h" #include "celix_convert_utils.h" -#include "celix_properties_constants.h" +#include "celix_utils_private_constants.h" static const char* const CELIX_PROPERTIES_BOOL_TRUE_STRVAL = "true"; static const char* const CELIX_PROPERTIES_BOOL_FALSE_STRVAL = "false"; diff --git a/libs/utils/src/utils.c b/libs/utils/src/utils.c index 36919e0d3..dac59f37b 100644 --- a/libs/utils/src/utils.c +++ b/libs/utils/src/utils.c @@ -25,14 +25,12 @@ #include "utils.h" #include "celix_utils.h" - +#include "celix_utils_private_constants.h" #ifdef __APPLE__ #include "memstream/open_memstream.h" #else #include -#include - #endif unsigned int utils_stringHash(const void* strPtr) { @@ -84,7 +82,7 @@ char* celix_utils_makeCIdentifier(const char* s) { if (celix_utils_isStringNullOrEmpty(s)) { return NULL; } - size_t len = strnlen(s, CELIX_UTILS_MAX_STRLEN); + size_t len = celix_utils_strlen(s); char* ns = malloc(len + 2); //+2 for '\0' and an extra _ prefix if needed. int i = 0; if (isdigit(s[0])) { @@ -165,7 +163,7 @@ bool utils_isStringEmptyOrNull(const char * const str) { bool empty = true; if (str != NULL) { int i; - for (i = 0; i < strnlen(str, 1024 * 1024); i += 1) { + for (i = 0; i < celix_utils_strlen(str); i += 1) { if (!isspace(str[i])) { empty = false; break; @@ -315,6 +313,9 @@ char* celix_utils_strdup(const char *str) { } } +size_t celix_utils_strlen(const char *str) { + return str ? strnlen(str, CELIX_UTILS_MAX_STRLEN) : 0; +} void celix_utils_extractLocalNameAndNamespaceFromFullyQualifiedName(const char *fullyQualifiedName, const char *namespaceSeparator, char **outLocalName, char **outNamespace) { assert(namespaceSeparator != NULL); From 229a90b292704c0cc881a047f022641185bfe914 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 11 Nov 2023 17:07:03 +0800 Subject: [PATCH 47/63] Remove incorrect celix_steal_ptr usage. --- libs/utils/src/properties.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 542e1d2b2..def4df4c6 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -653,7 +653,7 @@ celix_properties_t* celix_properties_copy(const celix_properties_t* properties) return NULL; } } - return celix_steal_ptr(copy); + return copy; } celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties, const char* key) { From e981ee4f7a8eb37b760e8aa59c33a94a0ed6ea7f Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 11 Nov 2023 17:17:00 +0800 Subject: [PATCH 48/63] Minor documentation improvement. --- libs/utils/include/celix_string_hash_map.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index b0e8c6ff3..13615380a 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -121,7 +121,7 @@ typedef struct celix_string_hash_map_create_options { * (celix_stringHashMap_hasKey). * * If the key is already in use, the celix_stringHashMap_put* will not store the provided key and will - * instead keep the already existing key. If needed, the caller should free the provided key. + * instead keep the already existing key. If needed, the caller may free the provided key. * If the key is not already in use, the celix_stringHashMap_put* will store the provided key and the caller * should not free the provided key. * From 3964e0b242d8f87a553bf519f8738fae0b64b633 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 12 Nov 2023 13:26:09 +0100 Subject: [PATCH 49/63] Refactor hash map long hash, load factor and hash function usage based on benchmark output Also add internal header for long/string hash map and properties so that hash map statistics can be extracted - in the project - for analysing purposes. --- libs/utils/CMakeLists.txt | 2 + libs/utils/benchmark/CMakeLists.txt | 14 +- .../benchmark/src/LongHashmapBenchmark.cc | 37 ++- .../benchmark/src/StringHashmapBenchmark.cc | 43 +-- .../src/HashMapErrorInjectionTestSuite.cc | 2 +- libs/utils/gtest/src/HashMapTestSuite.cc | 35 ++- libs/utils/gtest/src/PropertiesTestSuite.cc | 22 ++ libs/utils/include/celix_long_hash_map.h | 18 +- libs/utils/include/celix_properties.h | 28 +- libs/utils/include/celix_string_hash_map.h | 18 +- libs/utils/include/celix_version.h | 8 +- .../celix_hash_map_internal.h | 62 +++++ .../celix_properties_internal.h | 46 ++++ libs/utils/src/celix_hash_map.c | 250 +++++++++++------- libs/utils/src/celix_hash_map_private.h | 9 +- libs/utils/src/properties.c | 5 + 16 files changed, 432 insertions(+), 167 deletions(-) create mode 100644 libs/utils/include_internal/celix_hash_map_internal.h create mode 100644 libs/utils/include_internal/celix_properties_internal.h diff --git a/libs/utils/CMakeLists.txt b/libs/utils/CMakeLists.txt index 2738a148e..f9b012602 100644 --- a/libs/utils/CMakeLists.txt +++ b/libs/utils/CMakeLists.txt @@ -77,6 +77,7 @@ if (UTILS) target_include_directories(utils PUBLIC $ + $ #Note not installed, only for project internal usage $ ) target_include_directories(utils PRIVATE src include_deprecated) @@ -127,6 +128,7 @@ if (UTILS) target_compile_definitions(utils_cut PRIVATE CELIX_UTILS_STATIC_DEFINE) target_include_directories(utils_cut PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_CURRENT_LIST_DIR}/include_internal ${MEMSTREAM_INCLUDE_DIR} ${CMAKE_BINARY_DIR}/celix/gen/includes/utils ${CMAKE_BINARY_DIR}/celix/gen/src/utils diff --git a/libs/utils/benchmark/CMakeLists.txt b/libs/utils/benchmark/CMakeLists.txt index 0d09b0c54..81892af9d 100644 --- a/libs/utils/benchmark/CMakeLists.txt +++ b/libs/utils/benchmark/CMakeLists.txt @@ -25,11 +25,19 @@ celix_subproject(UTILS_BENCHMARK "Option to enable Celix framework benchmark" ${ if (UTILS_BENCHMARK) find_package(benchmark REQUIRED) - add_executable(celix_utils_benchmark + add_executable(celix_string_hashmap_benchmark src/BenchmarkMain.cc src/StringHashmapBenchmark.cc + ) + target_link_libraries(celix_string_hashmap_benchmark PRIVATE Celix::utils benchmark::benchmark) + target_compile_options(celix_string_hashmap_benchmark PRIVATE -Wno-unused-function) + celix_deprecated_utils_headers(celix_string_hashmap_benchmark) + + add_executable(celix_long_hashmap_benchmark + src/BenchmarkMain.cc src/LongHashmapBenchmark.cc ) - target_link_libraries(celix_utils_benchmark PRIVATE Celix::utils benchmark::benchmark) - celix_deprecated_utils_headers(celix_utils_benchmark) + target_link_libraries(celix_long_hashmap_benchmark PRIVATE Celix::utils benchmark::benchmark) + target_compile_options(celix_long_hashmap_benchmark PRIVATE -Wno-unused-function) + celix_deprecated_utils_headers(celix_long_hashmap_benchmark) endif () diff --git a/libs/utils/benchmark/src/LongHashmapBenchmark.cc b/libs/utils/benchmark/src/LongHashmapBenchmark.cc index 3a3f648f2..7669fc889 100644 --- a/libs/utils/benchmark/src/LongHashmapBenchmark.cc +++ b/libs/utils/benchmark/src/LongHashmapBenchmark.cc @@ -26,12 +26,11 @@ #include "hash_map.h" #include "celix_long_hash_map.h" +#include "celix_hash_map_internal.h" class LongHashmapBenchmark { public: - explicit LongHashmapBenchmark(int64_t _nrOfEntries) : testVectorsMap{createRandomMap(_nrOfEntries)} { - deprecatedHashMap = hashMap_create(nullptr, nullptr, nullptr, nullptr); - } + explicit LongHashmapBenchmark(int64_t _nrOfEntries) : testVectorsMap{createRandomMap(_nrOfEntries)} {} ~LongHashmapBenchmark() { celix_longHashMap_destroy(celixHashMap); @@ -88,7 +87,7 @@ class LongHashmapBenchmark { long midEntryKey{0}; const std::unordered_map testVectorsMap; std::unordered_map stdMap{}; - hash_map_t* deprecatedHashMap{nullptr}; + hash_map_t* deprecatedHashMap{hashMap_create(nullptr, nullptr, nullptr, nullptr)}; celix_long_hash_map_t* celixHashMap{celix_longHashMap_create()}; }; @@ -123,6 +122,8 @@ static void LongHashmapBenchmark_addEntryToDeprecatedHashmap(benchmark::State& s hashMap_put(benchmark.deprecatedHashMap, (void*)42, (void*)42); } state.SetItemsProcessed(state.iterations()); + state.counters["average_bucket_size"] = NAN; + state.counters["stddev_bucket_size"] = NAN; } static void LongHashmapBenchmark_findEntryFromStdMap(benchmark::State& state) { @@ -153,7 +154,14 @@ static void LongHashmapBenchmark_findEntryFromCelixMap(benchmark::State& state) } } state.SetItemsProcessed(state.iterations()); + + auto stats = celix_longHashMap_getStatistics(benchmark.celixHashMap); + state.counters["nrOfBuckets"] = (double)stats.nrOfBuckets; + state.counters["resizeCount"] = (double)stats.resizeCount; + state.counters["averageNrOfEntriesPerBucket"] = stats.averageNrOfEntriesPerBucket; + state.counters["stdDeviationNrOfEntriesPerBucket"] = stats.stdDeviationNrOfEntriesPerBucket; } + static void LongHashmapBenchmark_findEntryFromDeprecatedMap(benchmark::State& state) { LongHashmapBenchmark benchmark{state.range(0)}; benchmark.fillDeprecatedCelixHashMap(); @@ -203,16 +211,17 @@ static void LongHashmapBenchmark_fillDeprecatedHashMap(benchmark::State& state) } #define CELIX_BENCHMARK(name) \ - BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kMicrosecond) + BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kNanosecond) \ + ->RangeMultiplier(10)->Range(10, 1000000) -CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToCelixHashmap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToDeprecatedHashmap)->RangeMultiplier(10)->Range(100, 10000); +CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToStdMap); //reference +CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToCelixHashmap); +CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToDeprecatedHashmap); -CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromCelixMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromDeprecatedMap)->RangeMultiplier(10)->Range(100, 10000); +CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromStdMap); //reference +CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromCelixMap); +CELIX_BENCHMARK(LongHashmapBenchmark_findEntryFromDeprecatedMap); -CELIX_BENCHMARK(LongHashmapBenchmark_fillStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(LongHashmapBenchmark_fillCelixHashMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(LongHashmapBenchmark_fillDeprecatedHashMap)->RangeMultiplier(10)->Range(100, 10000); +CELIX_BENCHMARK(LongHashmapBenchmark_fillStdMap); //reference +CELIX_BENCHMARK(LongHashmapBenchmark_fillCelixHashMap); +CELIX_BENCHMARK(LongHashmapBenchmark_fillDeprecatedHashMap); diff --git a/libs/utils/benchmark/src/StringHashmapBenchmark.cc b/libs/utils/benchmark/src/StringHashmapBenchmark.cc index 36b4b9560..a19fa25ed 100644 --- a/libs/utils/benchmark/src/StringHashmapBenchmark.cc +++ b/libs/utils/benchmark/src/StringHashmapBenchmark.cc @@ -18,7 +18,6 @@ */ #include -#include #include #include #include @@ -28,7 +27,8 @@ #include "hash_map.h" #include "celix_properties.h" #include "celix_string_hash_map.h" - +#include "celix_hash_map_internal.h" +#include "celix_properties_internal.h" class StringHashmapBenchmark { public: @@ -185,6 +185,12 @@ static void StringHashmapBenchmark_findEntryFromCelixMap(benchmark::State& state } } state.SetItemsProcessed(state.iterations()); + + auto stats = celix_stringHashMap_getStatistics(benchmark.celixHashMap); + state.counters["nrOfBuckets"] = (double)stats.nrOfBuckets; + state.counters["resizeCount"] = (double)stats.resizeCount; + state.counters["averageNrOfEntriesPerBucket"] = stats.averageNrOfEntriesPerBucket; + state.counters["stdDeviationNrOfEntriesPerBucket"] = stats.stdDeviationNrOfEntriesPerBucket; } static void StringHashmapBenchmark_findEntryFromDeprecatedMap(benchmark::State& state) { StringHashmapBenchmark benchmark{state.range(0)}; @@ -214,6 +220,12 @@ static void StringHashmapBenchmark_findEntryFromCelixProperties(benchmark::State } } state.SetItemsProcessed(state.iterations()); + + auto stats = celix_properties_getStatistics(benchmark.celixProperties); + state.counters["nrOfBuckets"] = (double)stats.nrOfBuckets; + state.counters["resizeCount"] = (double)stats.resizeCount; + state.counters["averageNrOfEntriesPerBucket"] = stats.averageNrOfEntriesPerBucket; + state.counters["stdDeviationNrOfEntriesPerBucket"] = stats.stdDeviationNrOfEntriesPerBucket; } static void StringHashmapBenchmark_fillStdMap(benchmark::State& state) { @@ -264,19 +276,20 @@ static void StringHashmapBenchmark_fillProperties(benchmark::State& state) { } #define CELIX_BENCHMARK(name) \ - BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kMicrosecond) + BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kMicrosecond) \ + ->RangeMultiplier(10)->Range(10, 1000000) -CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToCelixHashmap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToDeprecatedHashmap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToCelixProperties)->RangeMultiplier(10)->Range(100, 10000); +CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToStdMap); //reference +CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToCelixHashmap); +CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToDeprecatedHashmap); +CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToCelixProperties); -CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromCelixMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromDeprecatedMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromCelixProperties)->RangeMultiplier(10)->Range(100, 10000); +CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromStdMap); //reference +CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromCelixMap); +CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromDeprecatedMap); +CELIX_BENCHMARK(StringHashmapBenchmark_findEntryFromCelixProperties); -CELIX_BENCHMARK(StringHashmapBenchmark_fillStdMap)->RangeMultiplier(10)->Range(100, 10000); //reference -CELIX_BENCHMARK(StringHashmapBenchmark_fillCelixHashMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_fillDeprecatedHashMap)->RangeMultiplier(10)->Range(100, 10000); -CELIX_BENCHMARK(StringHashmapBenchmark_fillProperties)->RangeMultiplier(10)->Range(100, 10000); \ No newline at end of file +CELIX_BENCHMARK(StringHashmapBenchmark_fillStdMap); //reference +CELIX_BENCHMARK(StringHashmapBenchmark_fillCelixHashMap); +CELIX_BENCHMARK(StringHashmapBenchmark_fillDeprecatedHashMap); +CELIX_BENCHMARK(StringHashmapBenchmark_fillProperties); diff --git a/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc index 198a21863..ddeb9ef5d 100644 --- a/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc @@ -93,7 +93,7 @@ TEST_F(HashMapErrorInjectionTestSuite, PutFailureTest) { TEST_F(HashMapErrorInjectionTestSuite, ResizeFailureTest) { // Given a hashmap with a low load factor celix_long_hash_map_create_options_t opts{}; - opts.loadFactor = 0.1; + opts.maxLoadFactor = 0.1; celix_autoptr(celix_long_hash_map_t) lProps = celix_longHashMap_createWithOptions(&opts); // And when the hash map is filled 1 entry before the resize threshold diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index ffc2063a0..6ec34cc8a 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -19,8 +19,10 @@ #include +#include "celix_hash_map_private.h" #include "celix_long_hash_map.h" #include "celix_string_hash_map.h" +#include "celix_hash_map_internal.h" #include "celix_utils.h" #include #include @@ -97,6 +99,17 @@ class HashMapTestSuite : public ::testing::Test { } } + + void printStats(const char* keyType, const celix_hash_map_statistics_t* stats) { + printf("Hashmap statistics:\n"); + printf("|- key type: %s\n", keyType); + printf("|- nr of entries: %zu\n", stats->nrOfEntries); + printf("|- nr of buckets: %zu\n", stats->nrOfBuckets); + printf("|- average nr of entries in bucket: %f\n", stats->averageNrOfEntriesPerBucket); + printf("|- stddev nr of entries in bucket: %f\n", stats->stdDeviationNrOfEntriesPerBucket); + printf("|- resize count: %zu\n", stats->resizeCount); + } + private: std::default_random_engine generator{}; }; @@ -464,7 +477,7 @@ TEST_F(HashMapTestSuite, IterateStressTest) { TEST_F(HashMapTestSuite, IterateStressCapacityAndLoadFactorTest) { celix_string_hash_map_create_options sOpts{}; - sOpts.loadFactor = 10; // high load factor to ensure buckets have multiple entries for testing + sOpts.maxLoadFactor = 10; // high load factor to ensure buckets have multiple entries for testing sOpts.initialCapacity = 5; auto* sMap = celix_stringHashMap_createWithOptions(&sOpts); fillStringHashMap(sMap, 100); @@ -479,7 +492,7 @@ TEST_F(HashMapTestSuite, IterateStressCapacityAndLoadFactorTest) { celix_stringHashMap_destroy(sMap); celix_long_hash_map_create_options lOpts{}; - lOpts.loadFactor = 10; // high load factor to ensure buckets have multiple entries for testing + lOpts.maxLoadFactor = 10; // high load factor to ensure buckets have multiple entries for testing lOpts.initialCapacity = 5; auto* lMap = celix_longHashMap_createWithOptions(&lOpts); fillLongHashMap(lMap, 100); @@ -682,3 +695,21 @@ TEST_F(HashMapTestSuite, LongHashMapEqualsTest) { celix_longHashMap_putLong(map1, 3, 42); EXPECT_TRUE(celix_longHashMap_equals(map1, map2)); } + +TEST_F(HashMapTestSuite, GetStatsTest) { + celix_autoptr(celix_long_hash_map_t) map1 = celix_longHashMap_create(); + celix_autoptr(celix_string_hash_map_t) map2 = celix_stringHashMap_create(); + + for (int i = 0; i < 200; ++i) { + celix_longHashMap_putLong(map1, i, i); + celix_stringHashMap_putLong(map2, std::to_string(i).c_str(), i); + } + + celix_hash_map_statistics_t stats = celix_longHashMap_getStatistics(map1); + EXPECT_EQ(stats.nrOfEntries, 200); + printStats("long", &stats); + + stats = celix_stringHashMap_getStatistics(map2); + EXPECT_EQ(stats.nrOfEntries, 200); + printStats("string", &stats); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 75cdc5fde..1c6af59d4 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -20,6 +20,7 @@ #include #include "celix_properties.h" +#include "celix_properties_internal.h" #include "celix_utils.h" #include "properties.h" #include "celix_err.h" @@ -31,6 +32,15 @@ class PropertiesTestSuite : public ::testing::Test { PropertiesTestSuite() { celix_err_resetErrors(); } + + void printStats(const celix_hash_map_statistics_t* stats) { + printf("Properties statistics:\n"); + printf("|- nr of entries: %zu\n", stats->nrOfEntries); + printf("|- nr of buckets: %zu\n", stats->nrOfBuckets); + printf("|- average nr of entries in bucket: %f\n", stats->averageNrOfEntriesPerBucket); + printf("|- stddev nr of entries in bucket: %f\n", stats->stdDeviationNrOfEntriesPerBucket); + printf("|- resize count: %zu\n", stats->resizeCount); + } }; @@ -683,3 +693,15 @@ TEST_F(PropertiesTestSuite, InvalidArgumentsTest) { EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, celix_properties_setWithoutCopy(props, strdup("key"), nullptr)); EXPECT_EQ(2, celix_err_getErrorCount()); } + +TEST_F(PropertiesTestSuite, GetStatsTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + + for (int i = 0; i < 200; ++i) { + celix_properties_set(props, std::to_string(i).c_str(), std::to_string(i).c_str()); + } + + celix_hash_map_statistics_t stats = celix_properties_getStatistics(props); + EXPECT_EQ(stats.nrOfEntries, 200); + printStats(&stats); +} \ No newline at end of file diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index fc981c386..c86dba3a8 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -114,21 +114,21 @@ typedef struct celix_long_hash_map_create_options { unsigned int initialCapacity CELIX_OPTS_INIT; /** - * @brief The hash map load factor, which controls the max ratio between nr of entries in the hash map and the + * @brief The hash map max load factor, which controls the max ratio between nr of entries in the hash map and the * hash map capacity. * - * The load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries + * The max load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries * in the hash map. The load factor is an important property of the hash map which influences how close the * hash map performs to O(1) for its get, has and put operations. * - * If the nr of entries increases above the loadFactor * capacity, the hash capacity will be doubled. + * If the nr of entries increases above the maxLoadFactor * capacity, the hash table capacity will be doubled. * For example a hash map with capacity 16 and load factor 0.75 will double its capacity when the 13th entry * is added to the hash map. * * If 0 is provided, the hash map load factor will be 0.75 (default hash map load factor). * Default is 0. */ - double loadFactor CELIX_OPTS_INIT; + double maxLoadFactor CELIX_OPTS_INIT; } celix_long_hash_map_create_options_t; #ifndef __cplusplus @@ -140,7 +140,7 @@ typedef struct celix_long_hash_map_create_options { .removedCallbackData = NULL, \ .removedCallback = NULL, \ .initialCapacity = 0, \ - .loadFactor = 0 \ + .maxLoadFactor = 0 \ } #endif @@ -174,7 +174,7 @@ CELIX_UTILS_EXPORT size_t celix_longHashMap_size(const celix_long_hash_map_t* ma /** * @brief Add pointer entry the string hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] map The hashmap * @param[in] key The key to use. @@ -187,7 +187,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_put(celix_long_hash_map_t* m /** * @brief add long entry the long hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap * @param key The key to use. @@ -200,7 +200,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_putLong(celix_long_hash_map_ /** * @brief add double entry the long hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap * @param key The key to use. @@ -213,7 +213,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_longHashMap_putDouble(celix_long_hash_ma /** * @brief add bool entry the long hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap * @param key The key to use. diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index caade747c..6764858e4 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -117,7 +117,7 @@ typedef struct celix_properties_iterator { /** * @brief Create a new empty property set. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @return A new empty property set. */ @@ -135,7 +135,7 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_properties_t, celix_properties_destroy) /** * @brief Load properties from a file. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] filename The name of the file to load properties from. * @return A property set containing the properties from the file. @@ -146,7 +146,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filenam /** * @brief Load properties from a stream. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in,out] stream The stream to load properties from. * @return A property set containing the properties from the stream. @@ -157,7 +157,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* str /** * @brief Load properties from a string. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] input The string to load properties from. * @return A property set containing the properties from the string. @@ -170,7 +170,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const cha * * @note Properties values are always stored as string values, regardless of their actual underlining types. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to store. * @param[in] file The name of the file to store the properties to. @@ -217,7 +217,7 @@ CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const /** * @brief Set the value of a property. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -232,7 +232,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* prope /** * @brief Set the value of a property without copying the value string. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. This callee will take ownership of the key, so the key must not be @@ -264,7 +264,7 @@ celix_properties_getAsLong(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a long integer. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -291,7 +291,7 @@ celix_properties_getAsBool(const celix_properties_t* properties, const char* key /** * @brief Set the value of a property to a boolean. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -304,7 +304,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setBool(celix_properties_t* p /** * @brief Set the value of a property to a double. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -334,7 +334,7 @@ celix_properties_getAsDouble(const celix_properties_t* properties, const char* k * @brief Set the value of a property as a Celix version. * * This function will make a copy of the provided celix_version_t object and store it in the property set. - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -351,7 +351,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t * * This function will store a reference to the provided celix_version_t object in the property set and takes * ownership of the provided version. - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -400,7 +400,7 @@ CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion(const celix_pr /** * @brief Set the value of a property based on the provided property entry, maintaining underlying type. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. @@ -424,7 +424,7 @@ CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t* properties, c /** * @brief Make a copy of a properties set. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to copy. * @return A copy of the given property set. diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index b0e8c6ff3..7b3492d12 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -142,21 +142,21 @@ typedef struct celix_string_hash_map_create_options { unsigned int initialCapacity CELIX_OPTS_INIT; /** - * @brief The hash map load factor, which controls the max ratio between nr of entries in the hash map and the + * @brief The hash map max load factor, which controls the max ratio between nr of entries in the hash map and the * hash map capacity. * - * The load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries + * The max load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries * in the hash map. The load factor is an important property of the hash map which influences how close the * hash map performs to O(1) for its get, has and put operations. * - * If the nr of entries increases above the loadFactor * capacity, the hash capacity will be doubled. + * If the nr of entries increases above the maxLoadFactor * capacity, the hash table capacity will be doubled. * For example a hash map with capacity 16 and load factor 0.75 will double its capacity when the 13th entry * is added to the hash map. * * If 0 is provided, the hash map load factor will be 0.75 (default hash map load factor). * Default is 0. */ - double loadFactor CELIX_OPTS_INIT; + double maxLoadFactor CELIX_OPTS_INIT; } celix_string_hash_map_create_options_t; #ifndef __cplusplus @@ -170,7 +170,7 @@ typedef struct celix_string_hash_map_create_options { .removedKeyCallback = NULL, \ .storeKeysWeakly = false, \ .initialCapacity = 0, \ - .loadFactor = 0 \ + .maxLoadFactor = 0 \ } #endif @@ -204,7 +204,7 @@ CELIX_UTILS_EXPORT size_t celix_stringHashMap_size(const celix_string_hash_map_t /** * @brief Add pointer entry the string hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param[in] map The hashmap. * @param[in] key The key to use. The hashmap will create a copy if needed. @@ -217,7 +217,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_put(celix_string_hash_map_ /** * @brief add long entry the string hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. @@ -230,7 +230,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_putLong(celix_string_hash_ /** * @brief add double entry the string hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. @@ -242,7 +242,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_stringHashMap_putDouble(celix_string_has /** * @brief add bool entry the string hash map. * - * If the return status is an error, a error message is logged to celix_err. + * If the return status is an error, an error message is logged to celix_err. * * @param map The hashmap. * @param key The key to use. The hashmap will create a copy if needed. diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index 03cf0428d..34e67c25e 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -48,13 +48,15 @@ typedef struct celix_version celix_version_t; /** * @brief Create a new celix_version_t* using the supplied arguments. * + * If the return is NULL, an error message is logged to celix_err. + * * @param[in] major Major component of the version identifier. * @param[in] minor Minor component of the version identifier. * @param[in] micro Micro component of the version identifier. * @param[in] qualifier Qualifier component of the version identifier. If * NULL is specified, then the qualifier will be set to * the empty string. - * @return The created version or NULL if the input was incorrect + * @return The created version or NULL if the input was incorrect or memory could not be allocated. */ CELIX_UTILS_EXPORT celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier); @@ -164,14 +166,16 @@ CELIX_UTILS_EXPORT unsigned int celix_version_hash(const celix_version_t* versio /** * @brief Return the string representation of version identifier. * - *

* The format of the version string will be major.minor.micro * if qualifier is the empty string or * major.minor.micro.qualifier otherwise. * + * If the return is NULL, an error message is logged to celix_err. + * * @return The string representation of this version identifier. * @param version The celix_version_t* to get the string representation from. * @return Pointer to the string (char *) in which the result will be placed. Caller is owner of the string. + * NULL if memory could not be allocated. */ CELIX_UTILS_EXPORT char* celix_version_toString(const celix_version_t* version); diff --git a/libs/utils/include_internal/celix_hash_map_internal.h b/libs/utils/include_internal/celix_hash_map_internal.h new file mode 100644 index 000000000..a46f094d2 --- /dev/null +++ b/libs/utils/include_internal/celix_hash_map_internal.h @@ -0,0 +1,62 @@ +/* +* 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. +*/ + +/** + * @file celix_hash_map_internal.h + * @brief Header file the Apache Celix internal long and string hashmap API. + * The internal API is only meant to be used inside the Apache Celix project, so this is not part of the public API. + */ + +#ifndef CELIX_CELIX_HASH_MAP_INTERNAL_H +#define CELIX_CELIX_HASH_MAP_INTERNAL_H + +#include "celix_utils_export.h" +#include "celix_string_hash_map.h" +#include "celix_long_hash_map.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Statistics for a hash map. + */ +typedef struct celix_hash_map_statistics { + size_t nrOfEntries; + size_t nrOfBuckets; + size_t resizeCount; + double averageNrOfEntriesPerBucket; + double stdDeviationNrOfEntriesPerBucket; +} celix_hash_map_statistics_t; + +/** + * @brief Return the statistics for the given hash map. + */ +CELIX_UTILS_EXPORT celix_hash_map_statistics_t celix_longHashMap_getStatistics(const celix_long_hash_map_t* map); + +/** + * @brief Return the statistics for the given hash map. + */ +CELIX_UTILS_EXPORT celix_hash_map_statistics_t celix_stringHashMap_getStatistics(const celix_string_hash_map_t* map); + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_HASH_MAP_INTERNAL_H diff --git a/libs/utils/include_internal/celix_properties_internal.h b/libs/utils/include_internal/celix_properties_internal.h new file mode 100644 index 000000000..00cb3e4a3 --- /dev/null +++ b/libs/utils/include_internal/celix_properties_internal.h @@ -0,0 +1,46 @@ +/* +* 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. +*/ + +/** + * @file celix_properties_internal.h + * @brief Header file the Apache Celix internal properties API. + * The internal API is only meant to be used inside the Apache Celix project, so this is not part of the public API. + */ + +#ifndef CELIX_CELIX_PROPERTIES_INTERNAL_H +#define CELIX_CELIX_PROPERTIES_INTERNAL_H + +#include "celix_hash_map_internal.h" +#include "celix_properties.h" +#include "celix_utils_export.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Return the statistics for the underlining string hash map of the provided properties set. + */ +CELIX_UTILS_EXPORT celix_hash_map_statistics_t celix_properties_getStatistics(const celix_properties_t* properties); + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_PROPERTIES_INTERNAL_H diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index fa92ceb04..431759af8 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -20,6 +20,7 @@ #include "celix_string_hash_map.h" #include "celix_long_hash_map.h" #include "celix_hash_map_private.h" +#include "celix_hash_map_internal.h" #include #include @@ -31,18 +32,18 @@ #include "celix_err.h" #include "celix_stdlib_cleanup.h" -static unsigned int DEFAULT_INITIAL_CAPACITY = 16; -static double DEFAULT_LOAD_FACTOR = 0.75; -static unsigned int MAXIMUM_CAPACITY = INT32_MAX/10; - - +#define CELIX_HASHMAP_DEFAULT_INITIAL_CAPACITY 16 +#define CELIX_HASHMAP_DEFAULT_MAX_LOAD_FACTOR 5.0 +#define CELIX_HASHMAP_CAPACITY_INCREASE_FACTOR 2 +#define CELIX_HASHMAP_MAXIMUM_CAPACITY (INT32_MAX/10) +#define CELIX_HASHMAP_MAXIMUM_INCREASE_VALUE (1024*10) +#define CELIX_HASHMAP_HASH_PRIME 1610612741 union celix_hash_map_key { const char* strKey; long longKey; }; - struct celix_hash_map_entry { celix_hash_map_key_t key; celix_hash_map_value_t value; @@ -54,16 +55,19 @@ struct celix_hash_map { celix_hash_map_entry_t** buckets; unsigned int bucketsSize; //nr of buckets unsigned int size; //nr of total entries - double loadFactor; + double maxLoadFactor; celix_hash_map_key_type_e keyType; - unsigned int (*hashKeyFunction)(const celix_hash_map_key_t* key); - bool (*equalsKeyFunction)(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2); void (*simpleRemovedCallback)(void* value); void* removedCallbackData; void (*removedStringEntryCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue); void (*removedStringKeyCallback)(void* data, char* key); void (*removedLongEntryCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue); bool storeKeysWeakly; + + //statistics + size_t modificationsCount; + size_t resizeCount; + size_t collisionsCount; }; struct celix_string_hash_map { @@ -74,42 +78,52 @@ struct celix_long_hash_map { celix_hash_map_t genericMap; }; -static unsigned int celix_stringHashMap_hash(const celix_hash_map_key_t* key) { - return celix_utils_stringHash(key->strKey); -} - -static unsigned int celix_longHashMap_hash(const celix_hash_map_key_t* key) { - return key->longKey ^ (key->longKey >> (sizeof(key->longKey)*8/2)); -} +// hash1 (original) +//static unsigned int celix_longHashMap_hash(long key) { +// return key ^ (key >> (sizeof(key)*8/2)); +//} -static bool celix_stringHashMap_keyEquals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { - return celix_utils_stringEquals(key1->strKey, key2->strKey); -} +// hash2 (using long hash from deprecated maphash) +//static unsigned int celix_longHashMap_hash(long key) { +// long h = key; +// h += ~(h << 9); +// h ^= ((h >> 14) | (h << 18)); /* >>> */ +// h += (h << 4); +// h ^= ((h >> 10) | (h << 22)); /* >>> */ +// return h; +//} -static bool celix_longHashMap_keyEquals(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2) { - return key1->longKey == key2->longKey; +// hash3 (using a prime) +static unsigned int celix_longHashMap_hash(long key) { + return (key ^ (key >> (sizeof(key)*8/2)) * CELIX_HASHMAP_HASH_PRIME); } -static unsigned int celix_hashMap_threshold(celix_hash_map_t* map) { - return (unsigned int)floor((double)map->bucketsSize * map->loadFactor); +static bool celix_hashMap_needsResize(const celix_hash_map_t* map) { + double loadFactor = (double)map->size / (double)map->bucketsSize; + return loadFactor > map->maxLoadFactor; } static unsigned int celix_hashMap_indexFor(unsigned int h, unsigned int length) { - return h & (length - 1); + return h % length; } +/** + * @brief get entry from hash map. Long key is used if strKey is NULL. + */ static celix_hash_map_entry_t* celix_hashMap_getEntry(const celix_hash_map_t* map, const char* strKey, long longKey) { - celix_hash_map_key_t key; - if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { - key.strKey = strKey; - } else { - key.longKey = longKey; - } - unsigned int hash = map->hashKeyFunction(&key); + unsigned int hash = strKey ? celix_utils_stringHash(strKey) : celix_longHashMap_hash(longKey); unsigned int index = celix_hashMap_indexFor(hash, map->bucketsSize); - for (celix_hash_map_entry_t* entry = map->buckets[index]; entry != NULL; entry = entry->next) { - if (entry->hash == hash && map->equalsKeyFunction(&key, &entry->key)) { - return entry; + if (strKey) { + for (celix_hash_map_entry_t* entry = map->buckets[index]; entry != NULL; entry = entry->next) { + if (entry->hash == hash && celix_utils_stringEquals(strKey, entry->key.strKey)) { + return entry; + } + } + } else { + for (celix_hash_map_entry_t* entry = map->buckets[index]; entry != NULL; entry = entry->next) { + if (entry->hash == hash && longKey == entry->key.longKey) { + return entry; + } } } return NULL; @@ -151,11 +165,17 @@ bool celix_hashMap_hasKey(const celix_hash_map_t* map, const char* strKey, long return celix_hashMap_getEntry(map, strKey, longKey) != NULL; } -celix_status_t celix_hashMap_resize(celix_hash_map_t *map, size_t newCapacity) { - if (map->bucketsSize >= MAXIMUM_CAPACITY) { +celix_status_t celix_hashMap_resize(celix_hash_map_t *map) { + if (map->bucketsSize >= CELIX_HASHMAP_MAXIMUM_CAPACITY) { return CELIX_SUCCESS; } + size_t newCapacity = (size_t)floor((double)map->bucketsSize * CELIX_HASHMAP_CAPACITY_INCREASE_FACTOR); + if (map->bucketsSize > CELIX_HASHMAP_MAXIMUM_INCREASE_VALUE) { + //after a certain point, only increase with CELIX_HASHMAP_MAXIMUM_INCREASE_VALUE instead of a factor + newCapacity = map->bucketsSize + CELIX_HASHMAP_MAXIMUM_INCREASE_VALUE; + } + assert(newCapacity > map->bucketsSize); //hash map can only grow celix_hash_map_entry_t **newBuf = realloc(map->buckets, newCapacity * sizeof(celix_hash_map_entry_t *)); @@ -188,6 +208,7 @@ celix_status_t celix_hashMap_resize(celix_hash_map_t *map, size_t newCapacity) { //update bucketSize to new capacity map->bucketsSize = newCapacity; + map->resizeCount += 1; return CELIX_SUCCESS; } @@ -233,32 +254,36 @@ celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, memcpy(&newEntry->value, value, sizeof(*value)); newEntry->next = entry; map->buckets[bucketIndex] = newEntry; - if (map->size++ >= celix_hashMap_threshold(map)) { - return celix_hashMap_resize(map, 2 * map->bucketsSize); + map->size += 1; + if (celix_hashMap_needsResize(map)) { + return celix_hashMap_resize(map); } return CELIX_SUCCESS; } +/** + * @brief Put the value in the map. If long hash is used, strKey should be NULL. + */ static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, long longKey, const celix_hash_map_value_t* value, celix_hash_map_value_t* replacedValueOut) { celix_hash_map_key_t key; - if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { + if (strKey) { key.strKey = strKey; } else { key.longKey = longKey; } - unsigned int hash = map->hashKeyFunction(&key); - unsigned int index = celix_hashMap_indexFor(hash, map->bucketsSize); - for (celix_hash_map_entry_t* entry = map->buckets[index]; entry != NULL; entry = entry->next) { - if (entry->hash == hash && map->equalsKeyFunction(&key, &entry->key)) { - //entry found, replacing entry - if (replacedValueOut != NULL) { - *replacedValueOut = entry->value; - } - celix_hashMap_callRemovedCallback(map, entry); - memcpy(&entry->value, value, sizeof(*value)); - return CELIX_SUCCESS; + + celix_hash_map_entry_t* entryFound = celix_hashMap_getEntry(map, strKey, longKey); + if (entryFound) { + if (replacedValueOut != NULL) { + *replacedValueOut = entryFound->value; } + celix_hashMap_callRemovedCallback(map, entryFound); + memcpy(&entryFound->value, value, sizeof(*value)); + return CELIX_SUCCESS; } + + unsigned int hash = strKey ? celix_utils_stringHash(strKey) : celix_longHashMap_hash(longKey); + unsigned int index = celix_hashMap_indexFor(hash, map->bucketsSize); celix_status_t status = celix_hashMap_addEntry(map, hash, &key, value, index); if (status == CELIX_SUCCESS && replacedValueOut != NULL) { memset(replacedValueOut, 0, sizeof(*replacedValueOut)); @@ -266,14 +291,14 @@ static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* return status; } -static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v, void** previousValueOut) { +static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.ptrValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static celix_status_t celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v, long* previousValueOut) { +static celix_status_t celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.longValue = v; @@ -287,47 +312,46 @@ static celix_status_t celix_hashMap_putDouble(celix_hash_map_t* map, const char* return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } -static celix_status_t celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v, bool* previousValueOut) { +static celix_status_t celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.boolValue = v; return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); } +/** + * @brief Remove entry from hash map. If long hash is used, strKey should be NULL. + */ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long longKey) { - celix_hash_map_key_t key; - if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { - key.strKey = strKey; - } else { - key.longKey = longKey; - } - - unsigned int hash = map->hashKeyFunction(&key); + unsigned int hash = strKey ? celix_utils_stringHash(strKey) : celix_longHashMap_hash(longKey); unsigned int index = celix_hashMap_indexFor(hash, map->bucketsSize); celix_hash_map_entry_t* visit = map->buckets[index]; celix_hash_map_entry_t* removedEntry = NULL; celix_hash_map_entry_t* prev = NULL; while (visit != NULL) { - if (visit->hash == hash && map->equalsKeyFunction(&key, &visit->key)) { - //hash & equals -> found entry - map->size--; - if (map->buckets[index] == visit) { - //current entry is first entry in bucket, set next to first entry in bucket - map->buckets[index] = visit->next; - } else { - //entry is in between, update link of prev to next entry - prev->next = visit->next; + if (visit->hash == hash) { + bool equals = strKey ? celix_utils_stringEquals(strKey, visit->key.strKey) : longKey == visit->key.longKey; + if (equals) { + // hash & equals -> found entry + map->size -= 1; + if (map->buckets[index] == visit) { + // current entry is first entry in bucket, set next to first entry in bucket + map->buckets[index] = visit->next; + } else { + // entry is in between, update link of prev to next entry + prev->next = visit->next; + } + removedEntry = visit; + break; } - removedEntry = visit; - break; } prev = visit; visit = visit->next; } if (removedEntry != NULL) { char* removedKey = NULL; - if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { + if (strKey) { removedKey = (char*)removedEntry->key.strKey; } celix_hashMap_destroyRemovedEntry(map, removedEntry); @@ -343,15 +367,11 @@ celix_status_t celix_hashMap_init( celix_hash_map_t* map, celix_hash_map_key_type_e keyType, unsigned int initialCapacity, - double loadFactor, - unsigned int (*hashKeyFn)(const celix_hash_map_key_t*), - bool (*equalsKeyFn)(const celix_hash_map_key_t*, const celix_hash_map_key_t*)) { - map->loadFactor = loadFactor; + double maxLoadFactor) { + map->maxLoadFactor = maxLoadFactor; map->size = 0; map->bucketsSize = initialCapacity; map->keyType = keyType; - map->hashKeyFunction = hashKeyFn; - map->equalsKeyFunction = equalsKeyFn; map->simpleRemovedCallback = NULL; map->removedCallbackData = NULL; map->removedLongEntryCallback = NULL; @@ -359,6 +379,10 @@ celix_status_t celix_hashMap_init( map->removedStringKeyCallback = NULL; map->storeKeysWeakly = false; + map->modificationsCount = 0; + map->resizeCount = 0; + map->collisionsCount = 0; + map->buckets = calloc(initialCapacity, sizeof(celix_hash_map_entry_t*)); return map->buckets == NULL ? CELIX_ENOMEM : CELIX_SUCCESS; } @@ -425,9 +449,9 @@ celix_string_hash_map_t* celix_stringHashMap_createWithOptions(const celix_strin return NULL; } - unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; - double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac, celix_stringHashMap_hash, celix_stringHashMap_keyEquals); + unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : CELIX_HASHMAP_DEFAULT_INITIAL_CAPACITY; + double fac = opts->maxLoadFactor > 0 ? opts->maxLoadFactor : CELIX_HASHMAP_DEFAULT_MAX_LOAD_FACTOR; + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_STRING_KEY, cap, fac); if (status != CELIX_SUCCESS) { celix_err_push("Cannot initialize hash map"); return NULL; @@ -455,9 +479,9 @@ celix_long_hash_map_t* celix_longHashMap_createWithOptions(const celix_long_hash return NULL; } - unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : DEFAULT_INITIAL_CAPACITY; - double fac = opts->loadFactor > 0 ? opts->loadFactor : DEFAULT_LOAD_FACTOR; - celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac, celix_longHashMap_hash, celix_longHashMap_keyEquals); + unsigned int cap = opts->initialCapacity > 0 ? opts->initialCapacity : CELIX_HASHMAP_DEFAULT_INITIAL_CAPACITY; + double fac = opts->maxLoadFactor > 0 ? opts->maxLoadFactor : CELIX_HASHMAP_DEFAULT_MAX_LOAD_FACTOR; + celix_status_t status = celix_hashMap_init(&map->genericMap, CELIX_HASH_MAP_LONG_KEY, cap, fac); if (status != CELIX_SUCCESS) { celix_err_push("Cannot initialize hash map"); return NULL; @@ -534,19 +558,19 @@ bool celix_longHashMap_getBool(const celix_long_hash_map_t* map, long key, bool } celix_status_t celix_stringHashMap_put(celix_string_hash_map_t* map, const char* key, void* value) { - return celix_hashMap_put(&map->genericMap, key, 0, value, NULL); + return celix_hashMap_put(&map->genericMap, key, 0, value); } celix_status_t celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* value) { - return celix_hashMap_put(&map->genericMap, NULL, key, value, NULL); + return celix_hashMap_put(&map->genericMap, NULL, key, value); } celix_status_t celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long value) { - return celix_hashMap_putLong(&map->genericMap, key, 0, value, NULL); + return celix_hashMap_putLong(&map->genericMap, key, 0, value); } celix_status_t celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long value) { - return celix_hashMap_putLong(&map->genericMap, NULL, key, value, NULL); + return celix_hashMap_putLong(&map->genericMap, NULL, key, value); } celix_status_t celix_stringHashMap_putDouble(celix_string_hash_map_t* map, const char* key, double value) { @@ -558,11 +582,11 @@ celix_status_t celix_longHashMap_putDouble(celix_long_hash_map_t* map, long key, } celix_status_t celix_stringHashMap_putBool(celix_string_hash_map_t* map, const char* key, bool value) { - return celix_hashMap_putBool(&map->genericMap, key, 0, value, NULL); + return celix_hashMap_putBool(&map->genericMap, key, 0, value); } celix_status_t celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool value) { - return celix_hashMap_putBool(&map->genericMap, NULL, key, value, NULL); + return celix_hashMap_putBool(&map->genericMap, NULL, key, value); } bool celix_stringHashMap_hasKey(const celix_string_hash_map_t* map, const char* key) { @@ -599,8 +623,9 @@ static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_ } for (celix_hash_map_entry_t* entry = celix_hashMap_firstEntry(map1); entry != NULL; entry = celix_hashMap_nextEntry(map1, entry)) { - celix_hash_map_entry_t* entryMap2 = celix_hashMap_getEntry(map2, entry->key.strKey, entry->key.longKey); - + const char* strKey = map1->keyType == CELIX_HASH_MAP_STRING_KEY ? entry->key.strKey : NULL; + long longKey = map1->keyType == CELIX_HASH_MAP_LONG_KEY ? entry->key.longKey : 0; + celix_hash_map_entry_t* entryMap2 = celix_hashMap_getEntry(map2, strKey, longKey); //note using memcmp, so for void* values the pointer value is compared, not the value itself. if (entryMap2 == NULL || memcmp(&entryMap2->value, &entry->value, sizeof(entryMap2->value)) != 0) { return false; @@ -747,3 +772,42 @@ void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter) { celix_longHashMapIterator_next(iter); celix_hashMap_remove(map, NULL, key); } + +static int celix_hashMap_nrOfEntriesInBucket(const celix_hash_map_t* map, int bucketIndex) { + int cnt = 0; + celix_hash_map_entry_t* entry = map->buckets[bucketIndex]; + while (entry != NULL) { + cnt += 1; + entry = entry->next; + } + return cnt; +} + +static celix_hash_map_statistics_t celix_hashMap_getStatistics(const celix_hash_map_t* map) { + celix_hash_map_statistics_t stats; + stats.nrOfEntries = map->size; + stats.nrOfBuckets = map->bucketsSize; + stats.resizeCount = map->resizeCount; + + double avg = (double)map->size / (double)map->bucketsSize; //note avg == load factor + double stdDev = 0.0; + for (int i = 0; i < map->bucketsSize; ++i) { + int entriesInBucket = celix_hashMap_nrOfEntriesInBucket(map, i); + stdDev += (entriesInBucket - avg) * (entriesInBucket - avg); + } + stdDev = stdDev / map->bucketsSize; + stdDev = sqrt(stdDev); + + stats.averageNrOfEntriesPerBucket = avg; + stats.stdDeviationNrOfEntriesPerBucket = stdDev; + + return stats; +} + +celix_hash_map_statistics_t celix_longHashMap_getStatistics(const celix_long_hash_map_t* map) { + return celix_hashMap_getStatistics(&map->genericMap); +} + +celix_hash_map_statistics_t celix_stringHashMap_getStatistics(const celix_string_hash_map_t* map) { + return celix_hashMap_getStatistics(&map->genericMap); +} diff --git a/libs/utils/src/celix_hash_map_private.h b/libs/utils/src/celix_hash_map_private.h index a3c4d7d4f..148826bd9 100644 --- a/libs/utils/src/celix_hash_map_private.h +++ b/libs/utils/src/celix_hash_map_private.h @@ -20,6 +20,7 @@ /** * @file celix_hash_map_private.h * @brief Private header for celix_hash_map, with function used for whitebox testing. + * The symbols of private headers are not exported and as such cannot be used by other libraries, except for testing. */ #ifndef CELIX_CELIX_HASH_MAP_PRIVATE_H @@ -39,9 +40,9 @@ typedef union celix_hash_map_key celix_hash_map_key_t; // opaque typedef enum celix_hash_map_key_type { CELIX_HASH_MAP_STRING_KEY, CELIX_HASH_MAP_LONG_KEY } celix_hash_map_key_type_e; /** - * @brief Resizes the hash map to the given capacity using realloc. + * @brief Resizes the hash map to the next allowed size. */ -celix_status_t celix_hashMap_resize(celix_hash_map_t* map, size_t newCapacity); +celix_status_t celix_hashMap_resize(celix_hash_map_t* map); /** * @brief Add a new entry to the hash map. @@ -58,9 +59,7 @@ celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, celix_status_t celix_hashMap_init(celix_hash_map_t* map, celix_hash_map_key_type_e keyType, unsigned int initialCapacity, - double loadFactor, - unsigned int (*hashKeyFn)(const celix_hash_map_key_t*), - bool (*equalsKeyFn)(const celix_hash_map_key_t*, const celix_hash_map_key_t*)); + double maxLoadFactor); #ifdef __cplusplus } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index cfbd63794..cb40bbf5a 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -20,6 +20,7 @@ #include "properties.h" #include "celix_properties.h" #include "celix_properties_private.h" +#include "celix_properties_internal.h" #include #include @@ -927,3 +928,7 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const memcpy(&internalIterB, b->_data, sizeof(internalIterB)); return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); } + +celix_hash_map_statistics_t celix_properties_getStatistics(const celix_properties_t* properties) { + return celix_stringHashMap_getStatistics(properties->map); +} From 10e16f65caddd4e95237e96746eba352ac0414a7 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sun, 12 Nov 2023 19:01:29 +0100 Subject: [PATCH 50/63] Fix memleak in hash map by first resizing and then adding entry Also add some addition celix properties statistics. --- .../benchmark/src/LongHashmapBenchmark.cc | 4 +- .../benchmark/src/StringHashmapBenchmark.cc | 16 ++++--- libs/utils/gtest/src/PropertiesTestSuite.cc | 22 +++++---- libs/utils/include/celix_string_hash_map.h | 2 + .../celix_properties_internal.h | 12 ++++- libs/utils/src/celix_hash_map.c | 48 +++++++++++-------- libs/utils/src/celix_hash_map_private.h | 7 +-- libs/utils/src/properties.c | 16 ++++++- 8 files changed, 79 insertions(+), 48 deletions(-) diff --git a/libs/utils/benchmark/src/LongHashmapBenchmark.cc b/libs/utils/benchmark/src/LongHashmapBenchmark.cc index 7669fc889..5b0d9b219 100644 --- a/libs/utils/benchmark/src/LongHashmapBenchmark.cc +++ b/libs/utils/benchmark/src/LongHashmapBenchmark.cc @@ -122,8 +122,6 @@ static void LongHashmapBenchmark_addEntryToDeprecatedHashmap(benchmark::State& s hashMap_put(benchmark.deprecatedHashMap, (void*)42, (void*)42); } state.SetItemsProcessed(state.iterations()); - state.counters["average_bucket_size"] = NAN; - state.counters["stddev_bucket_size"] = NAN; } static void LongHashmapBenchmark_findEntryFromStdMap(benchmark::State& state) { @@ -212,7 +210,7 @@ static void LongHashmapBenchmark_fillDeprecatedHashMap(benchmark::State& state) #define CELIX_BENCHMARK(name) \ BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kNanosecond) \ - ->RangeMultiplier(10)->Range(10, 1000000) + ->RangeMultiplier(10)->Range(10, 100000) CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToStdMap); //reference CELIX_BENCHMARK(LongHashmapBenchmark_addEntryToCelixHashmap); diff --git a/libs/utils/benchmark/src/StringHashmapBenchmark.cc b/libs/utils/benchmark/src/StringHashmapBenchmark.cc index a19fa25ed..134a77dae 100644 --- a/libs/utils/benchmark/src/StringHashmapBenchmark.cc +++ b/libs/utils/benchmark/src/StringHashmapBenchmark.cc @@ -222,10 +222,14 @@ static void StringHashmapBenchmark_findEntryFromCelixProperties(benchmark::State state.SetItemsProcessed(state.iterations()); auto stats = celix_properties_getStatistics(benchmark.celixProperties); - state.counters["nrOfBuckets"] = (double)stats.nrOfBuckets; - state.counters["resizeCount"] = (double)stats.resizeCount; - state.counters["averageNrOfEntriesPerBucket"] = stats.averageNrOfEntriesPerBucket; - state.counters["stdDeviationNrOfEntriesPerBucket"] = stats.stdDeviationNrOfEntriesPerBucket; + state.counters["nrOfBuckets"] = (double)stats.mapStatistics.nrOfBuckets; + state.counters["resizeCount"] = (double)stats.mapStatistics.resizeCount; + state.counters["averageNrOfEntriesPerBucket"] = stats.mapStatistics.averageNrOfEntriesPerBucket; + state.counters["stdDeviationNrOfEntriesPerBucket"] = stats.mapStatistics.stdDeviationNrOfEntriesPerBucket; + state.counters["sizeOfKeysAndStringValues"] = (double)stats.sizeOfKeysAndStringValues; + state.counters["averageSizeOfKeysAndStringValues"] = (double)stats.averageSizeOfKeysAndStringValues; + state.counters["fillStringOptimizationBufferPercentage"] = stats.fillStringOptimizationBufferPercentage; + state.counters["fillEntriesOptimizationBufferPercentage"] = stats.fillEntriesOptimizationBufferPercentage; } static void StringHashmapBenchmark_fillStdMap(benchmark::State& state) { @@ -276,8 +280,8 @@ static void StringHashmapBenchmark_fillProperties(benchmark::State& state) { } #define CELIX_BENCHMARK(name) \ - BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kMicrosecond) \ - ->RangeMultiplier(10)->Range(10, 1000000) + BENCHMARK(name)->MeasureProcessCPUTime()->UseRealTime()->Unit(benchmark::kNanosecond) \ + ->RangeMultiplier(10)->Range(10, 100000) CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToStdMap); //reference CELIX_BENCHMARK(StringHashmapBenchmark_addEntryToCelixHashmap); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 1c6af59d4..7dd9cf582 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -33,13 +33,17 @@ class PropertiesTestSuite : public ::testing::Test { celix_err_resetErrors(); } - void printStats(const celix_hash_map_statistics_t* stats) { + void printStats(const celix_properties_statistics_t* stats) { printf("Properties statistics:\n"); - printf("|- nr of entries: %zu\n", stats->nrOfEntries); - printf("|- nr of buckets: %zu\n", stats->nrOfBuckets); - printf("|- average nr of entries in bucket: %f\n", stats->averageNrOfEntriesPerBucket); - printf("|- stddev nr of entries in bucket: %f\n", stats->stdDeviationNrOfEntriesPerBucket); - printf("|- resize count: %zu\n", stats->resizeCount); + printf("|- nr of entries: %zu\n", stats->mapStatistics.nrOfEntries); + printf("|- nr of buckets: %zu\n", stats->mapStatistics.nrOfBuckets); + printf("|- average nr of entries in bucket: %f\n", stats->mapStatistics.averageNrOfEntriesPerBucket); + printf("|- stddev nr of entries in bucket: %f\n", stats->mapStatistics.stdDeviationNrOfEntriesPerBucket); + printf("|- resize count: %zu\n", stats->mapStatistics.resizeCount); + printf("|- size of keys and string values: %zu bytes\n", stats->sizeOfKeysAndStringValues); + printf("|- average size of keys and string values: %f bytes\n", stats->averageSizeOfKeysAndStringValues); + printf("|- fill string optimization buffer percentage: %f\n", stats->fillStringOptimizationBufferPercentage); + printf("|- fill entries optimization buffer percentage: %f\n", stats->fillEntriesOptimizationBufferPercentage); } }; @@ -701,7 +705,7 @@ TEST_F(PropertiesTestSuite, GetStatsTest) { celix_properties_set(props, std::to_string(i).c_str(), std::to_string(i).c_str()); } - celix_hash_map_statistics_t stats = celix_properties_getStatistics(props); - EXPECT_EQ(stats.nrOfEntries, 200); + auto stats = celix_properties_getStatistics(props); + EXPECT_EQ(stats.mapStatistics.nrOfEntries, 200); printStats(&stats); -} \ No newline at end of file +} diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index e64fb7f8f..186012d0b 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -125,6 +125,8 @@ typedef struct celix_string_hash_map_create_options { * If the key is not already in use, the celix_stringHashMap_put* will store the provided key and the caller * should not free the provided key. * + * Uf a celix_stringHashMap_put* returns a error, the caller may free the provided key. + * * @note This changes the default behaviour of the celix_stringHashMap_put* functions. * * Default is false. diff --git a/libs/utils/include_internal/celix_properties_internal.h b/libs/utils/include_internal/celix_properties_internal.h index 00cb3e4a3..34df8eb12 100644 --- a/libs/utils/include_internal/celix_properties_internal.h +++ b/libs/utils/include_internal/celix_properties_internal.h @@ -34,10 +34,18 @@ extern "C" { #endif +typedef struct celix_properties_statistics_t { + celix_hash_map_statistics_t mapStatistics; /**< The statistics of the underlining hash map. */ + size_t sizeOfKeysAndStringValues; /**< The size of the keys and string value representations in bytes. */ + double averageSizeOfKeysAndStringValues; /**< The average size of the keys and string values in bytes. */ + double fillStringOptimizationBufferPercentage; /**< The percentage of the fill string optimization buffer. */ + double fillEntriesOptimizationBufferPercentage; /**< The percentage of the fill entries optimization buffer. */ +} celix_properties_statistics_t; + /** - * @brief Return the statistics for the underlining string hash map of the provided properties set. + * @brief Return the statistics for the of the provided properties set. */ -CELIX_UTILS_EXPORT celix_hash_map_statistics_t celix_properties_getStatistics(const celix_properties_t* properties); +CELIX_UTILS_EXPORT celix_properties_statistics_t celix_properties_getStatistics(const celix_properties_t* properties); #ifdef __cplusplus } diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 431759af8..93e14d351 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -98,8 +98,11 @@ static unsigned int celix_longHashMap_hash(long key) { return (key ^ (key >> (sizeof(key)*8/2)) * CELIX_HASHMAP_HASH_PRIME); } +/** + * @brief Check if hash map needs to be resized if a extra entry is added. + */ static bool celix_hashMap_needsResize(const celix_hash_map_t* map) { - double loadFactor = (double)map->size / (double)map->bucketsSize; + double loadFactor = (double)(map->size +1) / (double)map->bucketsSize; return loadFactor > map->maxLoadFactor; } @@ -237,7 +240,18 @@ static void celix_hashMap_destroyRemovedEntry(celix_hash_map_t* map, celix_hash_ free(removedEntry); } -celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value, unsigned int bucketIndex) { +celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value) { + //resize (if needed) first, so that if allocation fails, no entry is yet created + if (celix_hashMap_needsResize(map)) { + celix_status_t status = celix_hashMap_resize(map); + if (status != CELIX_SUCCESS) { + return status; + } + } + + bool isStringKey = map->keyType == CELIX_HASH_MAP_STRING_KEY; + unsigned int hash = isStringKey ? celix_utils_stringHash(key->strKey) : celix_longHashMap_hash(key->longKey); + unsigned int bucketIndex = celix_hashMap_indexFor(hash, map->bucketsSize); celix_hash_map_entry_t* entry = map->buckets[bucketIndex]; celix_hash_map_entry_t* newEntry = malloc(sizeof(*newEntry)); if (!newEntry) { @@ -246,7 +260,7 @@ celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, } newEntry->hash = hash; - if (map->keyType == CELIX_HASH_MAP_STRING_KEY) { + if (isStringKey) { newEntry->key.strKey = map->storeKeysWeakly ? key->strKey : celix_utils_strdup(key->strKey); } else { newEntry->key.longKey = key->longKey; @@ -255,16 +269,14 @@ celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, newEntry->next = entry; map->buckets[bucketIndex] = newEntry; map->size += 1; - if (celix_hashMap_needsResize(map)) { - return celix_hashMap_resize(map); - } + return CELIX_SUCCESS; } /** * @brief Put the value in the map. If long hash is used, strKey should be NULL. */ -static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, long longKey, const celix_hash_map_value_t* value, celix_hash_map_value_t* replacedValueOut) { +static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, long longKey, const celix_hash_map_value_t* value) { celix_hash_map_key_t key; if (strKey) { key.strKey = strKey; @@ -274,20 +286,14 @@ static celix_status_t celix_hashMap_putValue(celix_hash_map_t* map, const char* celix_hash_map_entry_t* entryFound = celix_hashMap_getEntry(map, strKey, longKey); if (entryFound) { - if (replacedValueOut != NULL) { - *replacedValueOut = entryFound->value; - } + //replace value celix_hashMap_callRemovedCallback(map, entryFound); memcpy(&entryFound->value, value, sizeof(*value)); return CELIX_SUCCESS; } - unsigned int hash = strKey ? celix_utils_stringHash(strKey) : celix_longHashMap_hash(longKey); - unsigned int index = celix_hashMap_indexFor(hash, map->bucketsSize); - celix_status_t status = celix_hashMap_addEntry(map, hash, &key, value, index); - if (status == CELIX_SUCCESS && replacedValueOut != NULL) { - memset(replacedValueOut, 0, sizeof(*replacedValueOut)); - } + //new entry + celix_status_t status = celix_hashMap_addEntry(map, &key, value); return status; } @@ -295,28 +301,28 @@ static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKe celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.ptrValue = v; - return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); + return celix_hashMap_putValue(map, strKey, longKey, &value); } static celix_status_t celix_hashMap_putLong(celix_hash_map_t* map, const char* strKey, long longKey, long v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.longValue = v; - return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); + return celix_hashMap_putValue(map, strKey, longKey, &value); } static celix_status_t celix_hashMap_putDouble(celix_hash_map_t* map, const char* strKey, long longKey, double v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.doubleValue = v; - return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); + return celix_hashMap_putValue(map, strKey, longKey, &value); } static celix_status_t celix_hashMap_putBool(celix_hash_map_t* map, const char* strKey, long longKey, bool v) { celix_hash_map_value_t value; memset(&value, 0, sizeof(value)); value.boolValue = v; - return celix_hashMap_putValue(map, strKey, longKey, &value, NULL); + return celix_hashMap_putValue(map, strKey, longKey, &value); } /** @@ -722,7 +728,7 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { iter->value = entry->value; } else { iter->_internal[1] = NULL; - iter->key = NULL; + iter->key = ""; memset(&iter->value, 0, sizeof(iter->value)); } } diff --git a/libs/utils/src/celix_hash_map_private.h b/libs/utils/src/celix_hash_map_private.h index 148826bd9..4fb97a24f 100644 --- a/libs/utils/src/celix_hash_map_private.h +++ b/libs/utils/src/celix_hash_map_private.h @@ -47,11 +47,8 @@ celix_status_t celix_hashMap_resize(celix_hash_map_t* map); /** * @brief Add a new entry to the hash map. */ -celix_status_t celix_hashMap_addEntry(celix_hash_map_t* map, - unsigned int hash, - const celix_hash_map_key_t* key, - const celix_hash_map_value_t* value, - unsigned int bucketIndex); +celix_status_t +celix_hashMap_addEntry(celix_hash_map_t* map, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value); /** * @brief Initialize the hash map. diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 6df827d66..af0320594 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -929,6 +929,18 @@ bool celix_propertiesIterator_equals(const celix_properties_iterator_t* a, const return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); } -celix_hash_map_statistics_t celix_properties_getStatistics(const celix_properties_t* properties) { - return celix_stringHashMap_getStatistics(properties->map); +celix_properties_statistics_t celix_properties_getStatistics(const celix_properties_t* properties) { + size_t sizeOfKeysAndStringValues = 0; + CELIX_PROPERTIES_ITERATE(properties, iter) { + sizeOfKeysAndStringValues += celix_utils_strlen(iter.key) + 1; + sizeOfKeysAndStringValues += celix_utils_strlen(iter.entry.value) + 1; + } + + celix_properties_statistics_t stats; + stats.sizeOfKeysAndStringValues = sizeOfKeysAndStringValues; + stats.averageSizeOfKeysAndStringValues = (double)sizeOfKeysAndStringValues / celix_properties_size(properties) * 2; + stats.fillStringOptimizationBufferPercentage = (double)properties->currentStringBufferIndex / CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE; + stats.fillEntriesOptimizationBufferPercentage = (double)properties->currentEntriesBufferIndex / CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE; + stats.mapStatistics = celix_stringHashMap_getStatistics(properties->map); + return stats; } From 8248da534c960be52ac45a1b038fd5a94c9f5793 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Mon, 13 Nov 2023 21:29:15 +0800 Subject: [PATCH 51/63] Update hash map maxLoadFactor documentation. --- libs/utils/include/celix_long_hash_map.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index c86dba3a8..8e71d9a8b 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -125,7 +125,7 @@ typedef struct celix_long_hash_map_create_options { * For example a hash map with capacity 16 and load factor 0.75 will double its capacity when the 13th entry * is added to the hash map. * - * If 0 is provided, the hash map load factor will be 0.75 (default hash map load factor). + * If 0 is provided, the hash map load factor will be 5 (default hash map load factor). * Default is 0. */ double maxLoadFactor CELIX_OPTS_INIT; From d1206d9878136592a0afbf02cfe0d75b9c1bce1c Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Fri, 17 Nov 2023 20:33:05 +0100 Subject: [PATCH 52/63] Refactor celix_properties fill/create entry Also add/improve some additional error handling in properties. --- .../celix_version/CMakeLists.txt | 1 + .../celix_version/include/celix_version_ei.h | 2 + .../celix_version/src/celix_version_ei.cc | 8 + libs/utils/gtest/CMakeLists.txt | 1 + .../src/PropertiesErrorInjectionTestSuite.cc | 17 +++ libs/utils/gtest/src/PropertiesTestSuite.cc | 17 ++- libs/utils/src/properties.c | 142 ++++++++++-------- 7 files changed, 126 insertions(+), 62 deletions(-) diff --git a/libs/utils/error_injector/celix_version/CMakeLists.txt b/libs/utils/error_injector/celix_version/CMakeLists.txt index a89f7d9d5..bbdc8aced 100644 --- a/libs/utils/error_injector/celix_version/CMakeLists.txt +++ b/libs/utils/error_injector/celix_version/CMakeLists.txt @@ -22,5 +22,6 @@ target_link_libraries(version_ei PUBLIC Celix::error_injector Celix::utils) # It plays nicely with address sanitizer this way. target_link_options(version_ei INTERFACE LINKER:--wrap,celix_version_createVersionFromString + LINKER:--wrap,celix_version_copy ) add_library(Celix::version_ei ALIAS version_ei) diff --git a/libs/utils/error_injector/celix_version/include/celix_version_ei.h b/libs/utils/error_injector/celix_version/include/celix_version_ei.h index bccaff508..348612ad0 100644 --- a/libs/utils/error_injector/celix_version/include/celix_version_ei.h +++ b/libs/utils/error_injector/celix_version/include/celix_version_ei.h @@ -27,6 +27,8 @@ extern "C" { CELIX_EI_DECLARE(celix_version_createVersionFromString, celix_version_t*); +CELIX_EI_DECLARE(celix_version_copy, celix_version_t*); + #ifdef __cplusplus } #endif diff --git a/libs/utils/error_injector/celix_version/src/celix_version_ei.cc b/libs/utils/error_injector/celix_version/src/celix_version_ei.cc index 7a9b2f252..03ade7328 100644 --- a/libs/utils/error_injector/celix_version/src/celix_version_ei.cc +++ b/libs/utils/error_injector/celix_version/src/celix_version_ei.cc @@ -19,6 +19,7 @@ #include "celix_version_ei.h" extern "C" { + celix_version_t *__real_celix_version_createVersionFromString(const char *versionStr); CELIX_EI_DEFINE(celix_version_createVersionFromString, celix_version_t*) celix_version_t *__wrap_celix_version_createVersionFromString(const char *versionStr) { @@ -26,4 +27,11 @@ celix_version_t *__wrap_celix_version_createVersionFromString(const char *versio return __real_celix_version_createVersionFromString(versionStr); } +celix_version_t* __real_celix_version_copy(const celix_version_t* version); +CELIX_EI_DEFINE(celix_version_copy, celix_version_t*) +celix_version_t* __wrap_celix_version_copy(const celix_version_t* version) { + CELIX_EI_IMPL(celix_version_copy); + return __real_celix_version_copy(version); +} + } \ No newline at end of file diff --git a/libs/utils/gtest/CMakeLists.txt b/libs/utils/gtest/CMakeLists.txt index 784e8742e..bfb9a5531 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -113,6 +113,7 @@ if (EI_TESTS) Celix::asprintf_ei Celix::string_hash_map_ei Celix::long_hash_map_ei + Celix::version_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/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index ea507e2dd..468b45f32 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -26,12 +26,14 @@ #include "celix_properties.h" #include "celix_properties_private.h" #include "celix_utils_private_constants.h" +#include "celix_version.h" #include "celix_string_hash_map_ei.h" #include "celix_utils_ei.h" #include "malloc_ei.h" #include "stdio_ei.h" #include "celix_utils_ei.h" +#include "celix_version_ei.h" class PropertiesErrorInjectionTestSuite : public ::testing::Test { public: @@ -283,3 +285,18 @@ TEST_F(PropertiesErrorInjectionTestSuite, LoadFromStringFailureTest) { ASSERT_EQ(1, celix_err_getErrorCount()); celix_err_resetErrors(); } + +TEST_F(PropertiesErrorInjectionTestSuite, LoadSetVersionFailureTest) { + // Given a celix properties object + celix_autoptr(celix_properties_t) props = celix_properties_create(); + // And a version object + celix_autoptr(celix_version_t) version = celix_version_create(1, 2, 3, "qualifier"); + // When a celix_version_copy error injection is set for celix_properties_setVersion + celix_ei_expect_celix_version_copy((void*)celix_properties_setVersion, 0, nullptr); + // Then the celix_properties_setVersion call fails + auto status = celix_properties_setVersion(props, "key", version); + ASSERT_EQ(status, CELIX_ENOMEM); + //And a celix err msg is pushed + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); +} diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index 7dd9cf582..f77abf82a 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -19,11 +19,13 @@ #include +#include + +#include "celix_err.h" #include "celix_properties.h" #include "celix_properties_internal.h" #include "celix_utils.h" #include "properties.h" -#include "celix_err.h" using ::testing::MatchesRegex; @@ -709,3 +711,16 @@ TEST_F(PropertiesTestSuite, GetStatsTest) { EXPECT_EQ(stats.mapStatistics.nrOfEntries, 200); printStats(&stats); } + +TEST_F(PropertiesTestSuite, SetLongMaxMinTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + ASSERT_EQ(CELIX_SUCCESS, celix_properties_setLong(props, "max", LONG_MAX)); + ASSERT_EQ(CELIX_SUCCESS, celix_properties_setLong(props, "min", LONG_MIN)); + EXPECT_EQ(LONG_MAX, celix_properties_getAsLong(props, "max", 0)); + EXPECT_EQ(LONG_MIN, celix_properties_getAsLong(props, "min", 0)); +} + +TEST_F(PropertiesTestSuite, SetDoubleWithLargeStringRepresentationTest) { + celix_autoptr(celix_properties_t) props = celix_properties_create(); + ASSERT_EQ(CELIX_SUCCESS, celix_properties_setDouble(props, "large_str_value", 12345678901234567890.1234567890)); +} diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index af0320594..f511c4171 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -172,53 +172,50 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st */ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, celix_properties_entry_t* entry, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { - char convertedValueBuffer[32]; - if (strValue != NULL) { - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; - entry->value = celix_properties_createString(properties, *strValue); - entry->typed.strValue = entry->value; - } else if (longValue != NULL) { + const celix_properties_entry_t* prototype) { + char convertedValueBuffer[20]; + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; + entry->typed.versionValue = prototype->typed.versionValue; + bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer)); + if (written) { + entry->value = celix_properties_createString(properties, convertedValueBuffer); + } else { + entry->value = celix_version_toString(prototype->typed.versionValue); + } + } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; - entry->typed.longValue = *longValue; + entry->typed.longValue = prototype->typed.longValue; int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue); - if (written < 0 || written >= sizeof(convertedValueBuffer)) { + if (written < 0 || written <= sizeof(convertedValueBuffer)) { entry->value = celix_properties_createString(properties, convertedValueBuffer); } else { - char* val = NULL; - asprintf(&val, "%li", entry->typed.longValue); - entry->value = val; + //LCOV_EXCL_START + assert(false); // should not happen, LONG_MAX str is 19 chars, LONG_MIN str is 20 chars + //LCOV_EXCL_STOP } - } else if (doubleValue != NULL) { + } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; - entry->typed.doubleValue = *doubleValue; + entry->typed.doubleValue = prototype->typed.doubleValue; int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue); - if (written < 0 || written >= sizeof(convertedValueBuffer)) { + if (written < 0 || written <= sizeof(convertedValueBuffer)) { entry->value = celix_properties_createString(properties, convertedValueBuffer); } else { char* val = NULL; asprintf(&val, "%f", entry->typed.doubleValue); entry->value = val; } - } else if (boolValue != NULL) { + } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL; - entry->typed.boolValue = *boolValue; + entry->typed.boolValue = prototype->typed.boolValue; entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL; - } else /*versionValue*/ { - assert(versionValue != NULL); - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; - entry->typed.versionValue = versionValue; - bool written = celix_version_fillString(versionValue, convertedValueBuffer, sizeof(convertedValueBuffer)); - if (written) { - entry->value = celix_properties_createString(properties, convertedValueBuffer); - } else { - entry->value = celix_version_toString(versionValue); - } + } else /*string value*/ { + assert(prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING); + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + entry->value = celix_properties_createString(properties, prototype->typed.strValue); + entry->typed.strValue = entry->value; } + if (entry->value == NULL) { return CELIX_ENOMEM; } @@ -259,26 +256,26 @@ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_pr * Only 1 of the types values (strValue, LongValue, etc) should be provided. */ static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t* properties, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { + const celix_properties_entry_t* prototype) { celix_properties_entry_t* entry = celix_properties_allocEntry(properties); if (entry == NULL) { + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)prototype->typed.versionValue); + } celix_err_pushf("Cannot allocate property entry"); - celix_version_destroy(versionValue); return NULL; } - celix_status_t status = - celix_properties_fillEntry(properties, entry, strValue, longValue, doubleValue, boolValue, versionValue); + celix_status_t status = celix_properties_fillEntry(properties, entry, prototype); if (status != CELIX_SUCCESS) { celix_err_pushf("Cannot fill property entry"); - celix_version_destroy(versionValue); + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)prototype->typed.versionValue); + } if (entry >= properties->entriesBuffer && entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { - // entry is part of the properties entries buffer -> nop. + // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex + properties->currentEntriesBufferIndex -= 1; } else { free(entry); } @@ -303,28 +300,27 @@ static void celix_properties_destroyEntry(celix_properties_t* properties, celix_ /** * Create and add entry and optionally use the short properties optimization buffers. - * Only 1 of the types values (strValue, LongValue, etc) should be provided. + * The prototype is used to determine the type of the value. */ static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* properties, const char* key, - const char** strValue, - const long* longValue, - const double* doubleValue, - const bool* boolValue, - celix_version_t* versionValue) { + const celix_properties_entry_t* prototype) { if (!properties) { - celix_version_destroy(versionValue); + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)prototype->typed.versionValue); + } return CELIX_SUCCESS; // silently ignore } if (!key) { - celix_version_destroy(versionValue); + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)prototype->typed.versionValue); + } celix_err_pushf("Cannot set property with NULL key"); return CELIX_ILLEGAL_ARGUMENT; } - celix_properties_entry_t* entry = - celix_properties_createEntry(properties, strValue, longValue, doubleValue, boolValue, versionValue); + celix_properties_entry_t* entry = celix_properties_createEntry(properties, prototype); if (!entry) { return CELIX_ENOMEM; } @@ -587,10 +583,10 @@ celix_status_t celix_properties_store(celix_properties_t* properties, const char celix_err_push("Header cannot contain newlines. Ignoring header."); } else if (header) { rc = fputc('#', file); - if (rc != 0) { + if (rc != EOF) { rc = fputs(header, file); } - if (rc != 0) { + if (rc != EOF) { rc = fputc('\n', file); } } @@ -679,7 +675,10 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr } celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char* value) { - return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL); + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + prototype.typed.strValue = value; + return celix_properties_createAndSetEntry(properties, key, &prototype); } celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) { @@ -770,7 +769,10 @@ long celix_properties_getAsLong(const celix_properties_t* props, const char* key } celix_status_t celix_properties_setLong(celix_properties_t* props, const char* key, long value) { - return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL); + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; + prototype.typed.longValue = value; + return celix_properties_createAndSetEntry(props, key, &prototype); } double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) { @@ -784,7 +786,10 @@ double celix_properties_getAsDouble(const celix_properties_t* props, const char* } celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* key, double val) { - return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL); + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; + prototype.typed.doubleValue = val; + return celix_properties_createAndSetEntry(props, key, &prototype); } bool celix_properties_getAsBool(const celix_properties_t* props, const char* key, bool defaultValue) { @@ -798,7 +803,10 @@ bool celix_properties_getAsBool(const celix_properties_t* props, const char* key } celix_status_t celix_properties_setBool(celix_properties_t* props, const char* key, bool val) { - return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL); + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL; + prototype.typed.boolValue = val; + return celix_properties_createAndSetEntry(props, key, &prototype); } const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties, @@ -828,12 +836,24 @@ celix_version_t* celix_properties_getAsVersion(const celix_properties_t* propert } celix_status_t -celix_properties_setVersion(celix_properties_t* properties, const char* key, const celix_version_t* version) { - return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version)); +celix_properties_setVersion(celix_properties_t* props, const char* key, const celix_version_t* version) { + celix_version_t* copy = celix_version_copy(version); + if (copy == NULL) { + celix_err_push("Failed to copy version"); + return CELIX_ENOMEM; + } + + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; + prototype.typed.versionValue = copy; + return celix_properties_createAndSetEntry(props, key, &prototype); } -celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) { - return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version); +celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* props, const char* key, celix_version_t* version) { + celix_properties_entry_t prototype; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; + prototype.typed.versionValue = version; + return celix_properties_createAndSetEntry(props, key, &prototype); } int celix_properties_size(const celix_properties_t* properties) { From 038808eebd74416abf67edd4748dd55a4058faa9 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 18 Nov 2023 12:51:01 +0800 Subject: [PATCH 53/63] Add an extra byte for the terminating null byte to the value buffer. --- libs/utils/src/properties.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index f511c4171..58efd92b3 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -173,7 +173,7 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, celix_properties_entry_t* entry, const celix_properties_entry_t* prototype) { - char convertedValueBuffer[20]; + char convertedValueBuffer[21]; if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; entry->typed.versionValue = prototype->typed.versionValue; From 804d633ea4ac9a076a3405550c1a05e1a47ab1ed Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 18 Nov 2023 13:22:55 +0800 Subject: [PATCH 54/63] Detect error condition in string formatting. --- libs/utils/src/properties.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 58efd92b3..16db1b97c 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -187,7 +187,7 @@ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; entry->typed.longValue = prototype->typed.longValue; int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue); - if (written < 0 || written <= sizeof(convertedValueBuffer)) { + if (written >= 0 || written < sizeof(convertedValueBuffer)) { entry->value = celix_properties_createString(properties, convertedValueBuffer); } else { //LCOV_EXCL_START From 74f95501d5b419fbee8cd155691648054abed9e1 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 18 Nov 2023 15:05:10 +0800 Subject: [PATCH 55/63] Detect error condition in string formatting and make celix_properties_fillEntry shorter. --- libs/utils/src/properties.c | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 16db1b97c..dc05e1dd5 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -173,10 +173,9 @@ static void celix_properties_freeString(celix_properties_t* properties, char* st static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, celix_properties_entry_t* entry, const celix_properties_entry_t* prototype) { - char convertedValueBuffer[21]; + char convertedValueBuffer[21] = {0}; + *entry = *prototype; if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; - entry->typed.versionValue = prototype->typed.versionValue; bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer)); if (written) { entry->value = celix_properties_createString(properties, convertedValueBuffer); @@ -184,21 +183,12 @@ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, entry->value = celix_version_toString(prototype->typed.versionValue); } } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; - entry->typed.longValue = prototype->typed.longValue; - int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue); - if (written >= 0 || written < sizeof(convertedValueBuffer)) { - entry->value = celix_properties_createString(properties, convertedValueBuffer); - } else { - //LCOV_EXCL_START - assert(false); // should not happen, LONG_MAX str is 19 chars, LONG_MIN str is 20 chars - //LCOV_EXCL_STOP - } + // LONG_MAX str is 19 chars, LONG_MIN str is 20 chars + (void)snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue); + entry->value = celix_properties_createString(properties, convertedValueBuffer); } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; - entry->typed.doubleValue = prototype->typed.doubleValue; int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue); - if (written < 0 || written <= sizeof(convertedValueBuffer)) { + if (written >= 0 || written < sizeof(convertedValueBuffer)) { entry->value = celix_properties_createString(properties, convertedValueBuffer); } else { char* val = NULL; @@ -206,12 +196,9 @@ static celix_status_t celix_properties_fillEntry(celix_properties_t* properties, entry->value = val; } } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL; - entry->typed.boolValue = prototype->typed.boolValue; entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL; } else /*string value*/ { assert(prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING); - entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; entry->value = celix_properties_createString(properties, prototype->typed.strValue); entry->typed.strValue = entry->value; } @@ -675,7 +662,7 @@ celix_properties_entry_t* celix_properties_getEntry(const celix_properties_t* pr } celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char* value) { - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; prototype.typed.strValue = value; return celix_properties_createAndSetEntry(properties, key, &prototype); @@ -769,7 +756,7 @@ long celix_properties_getAsLong(const celix_properties_t* props, const char* key } celix_status_t celix_properties_setLong(celix_properties_t* props, const char* key, long value) { - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG; prototype.typed.longValue = value; return celix_properties_createAndSetEntry(props, key, &prototype); @@ -786,7 +773,7 @@ double celix_properties_getAsDouble(const celix_properties_t* props, const char* } celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* key, double val) { - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; prototype.typed.doubleValue = val; return celix_properties_createAndSetEntry(props, key, &prototype); @@ -803,7 +790,7 @@ bool celix_properties_getAsBool(const celix_properties_t* props, const char* key } celix_status_t celix_properties_setBool(celix_properties_t* props, const char* key, bool val) { - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL; prototype.typed.boolValue = val; return celix_properties_createAndSetEntry(props, key, &prototype); @@ -843,14 +830,14 @@ celix_properties_setVersion(celix_properties_t* props, const char* key, const ce return CELIX_ENOMEM; } - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; prototype.typed.versionValue = copy; return celix_properties_createAndSetEntry(props, key, &prototype); } celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* props, const char* key, celix_version_t* version) { - celix_properties_entry_t prototype; + celix_properties_entry_t prototype = {0}; prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; prototype.typed.versionValue = version; return celix_properties_createAndSetEntry(props, key, &prototype); From 3ff70465891b340b12febcc9459413a830c297e1 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 18 Nov 2023 16:02:03 +0800 Subject: [PATCH 56/63] Fix initial hash table capacity in celix_properties. --- libs/utils/src/properties.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index dc05e1dd5..6df4b25ad 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -350,7 +350,7 @@ celix_properties_t* celix_properties_create() { if (props != NULL) { celix_string_hash_map_create_options_t opts = CELIX_EMPTY_STRING_HASH_MAP_CREATE_OPTIONS; opts.storeKeysWeakly = true; - opts.initialCapacity = (unsigned int)ceil(CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE / 0.75); + opts.initialCapacity = CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE; opts.removedCallbackData = props; opts.removedCallback = celix_properties_removeEntryCallback; opts.removedKeyCallback = celix_properties_removeKeyCallback; From 17068903cc281fbdb589939d696037bd797be2a2 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sat, 18 Nov 2023 16:13:42 +0800 Subject: [PATCH 57/63] Fix misplaced documentation and update it. --- libs/utils/src/properties.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 6df4b25ad..76a4fdc17 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -49,8 +49,9 @@ struct celix_properties { * String buffer used to store the first key/value entries, * so that in many cases - for usage in service properties - additional memory allocations are not needed. * - * @note based on some small testing most services properties seem to max out at 11 entries. - * So 16 (next factor 2 based value) seems like a good fit. + * @note based on some small testing most services properties seem to max around 300 bytes. + * So 128 (next factor 2 based value) seems like a good fit. + * The size is tunable by changing CMake cache variable CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE or Conan option celix_properties_optimization_string_buffer_size. */ char stringBuffer[CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE]; @@ -63,8 +64,9 @@ struct celix_properties { * Entries buffer used to store the first entries, so that in many cases additional memory allocation * can be prevented. * - * @note based on some small testing most services properties seem to max around 300 bytes. - * So 512 (next factor 2 based value) seems like a good fit. + * @note based on some small testing most services properties seem to max out at 11 entries. + * So 16 (next factor 2 based value) seems like a good fit. + * The size is tunable by changing CMake cache variable CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE or Conan option celix_properties_optimization_entries_buffer_size. */ celix_properties_entry_t entriesBuffer[CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE]; From 4d4e9f7eaa3d22c24b429cd208e7cf3f50a67ad9 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 18 Nov 2023 15:00:21 +0100 Subject: [PATCH 58/63] Refactor celix_properties_size return type to size_t Also improve properties error handling --- .../src/pubsub_wire_protocol_common.c | 2 +- .../src/discovery_zeroconf_announcer.c | 2 +- libs/framework/src/service_reference.c | 2 +- libs/utils/include/celix_properties.h | 14 +++-- libs/utils/src/celix_hash_map.c | 9 +-- libs/utils/src/properties.c | 63 ++++++++++--------- 6 files changed, 49 insertions(+), 43 deletions(-) diff --git a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c index ef17ed0a8..5010919c7 100644 --- a/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c +++ b/bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c @@ -73,7 +73,7 @@ static celix_status_t pubsubProtocol_addNetstringEntryToBuffer(char* buffer, siz } celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t* message, char** bufferInOut, size_t* bufferLengthInOut, size_t* bufferContentLengthOut) { - int metadataSize = message->metadata.metadata == NULL ? 0 : celix_properties_size(message->metadata.metadata); + size_t metadataSize = message->metadata.metadata == NULL ? 0 : celix_properties_size(message->metadata.metadata); bool reallocBuffer = false; bool encoded = false; diff --git a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c index f295c466a..f0cf81eb1 100644 --- a/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c +++ b/bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c @@ -378,7 +378,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno TXTRecordCreate(&txtRecord, sizeof(txtBuf), txtBuf); char propSizeStr[16]= {0}; - sprintf(propSizeStr, "%d", celix_properties_size(entry->properties) + 1); + sprintf(propSizeStr, "%zu", celix_properties_size(entry->properties) + 1); (void)TXTRecordSetValue(&txtRecord, DZC_SERVICE_PROPERTIES_SIZE_KEY, strlen(propSizeStr), propSizeStr); if (!discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(announcer, &propIter, &txtRecord, sizeof(txtBuf), splitTxtRecord)) { TXTRecordDeallocate(&txtRecord); diff --git a/libs/framework/src/service_reference.c b/libs/framework/src/service_reference.c index ed1d98ea8..21d1a2829 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -207,7 +207,7 @@ celix_status_t serviceReference_getPropertyKeys(service_reference_pt ref, char * return status; } - *size = celix_properties_size(props); + *size = (unsigned int)celix_properties_size(props); *keys = malloc((*size) * sizeof(**keys)); if (!*keys) { return ENOMEM; diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index 6764858e4..e94e2d9d9 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -241,7 +241,7 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* prope * not be used after calling this function. The value should be deallocated with free. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry * and CELIX_ILLEGAL_ARGUMENT if the provided key or value is NULL. - * When CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function. + * When an error status is returned, the key and value will be freed by this function. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, @@ -357,7 +357,9 @@ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersion(celix_properties_t * @param[in] key The key of the property to set. * @param[in] version The value to set. The function will store a reference to this object in the property set and * takes ownership of the provided version. - * + @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry + * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. When an error status is returned, + * the version will be destroy with celix_version_destroy by this function. */ CELIX_UTILS_EXPORT celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version); @@ -400,11 +402,15 @@ CELIX_UTILS_EXPORT celix_version_t* celix_properties_getAsVersion(const celix_pr /** * @brief Set the value of a property based on the provided property entry, maintaining underlying type. * + * The typed entry value will be copied, which means that this function will use the entry.typed.strValue, + * entry.typed.longValue, entry.typed.doubleValue, entry.typed.boolValue or entry.typed.versionValue depending on + * the entry.valueType. The entry.strValue will be ignored. + * * If the return status is an error, an error message is logged to celix_err. * * @param[in] properties The property set to modify. * @param[in] key The key of the property to set. - * @param[in] entry The entry to set the property to. The entry will be copied, so it can be freed after calling + * @param[in] entry The entry to set the property to. The typed entry will be copied, so it can be freed after calling * this function. * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry * and CELIX_ILLEGAL_ARGUMENT if the provided key is NULL. @@ -437,7 +443,7 @@ CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_propert * @param[in] properties The property set to get the size of. * @return The number of properties in the property set. */ -CELIX_UTILS_EXPORT int celix_properties_size(const celix_properties_t* properties); +CELIX_UTILS_EXPORT size_t celix_properties_size(const celix_properties_t* properties); /** * @brief Check whether the provided property sets are equal. diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 93e14d351..aa2dd5721 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -65,9 +65,7 @@ struct celix_hash_map { bool storeKeysWeakly; //statistics - size_t modificationsCount; size_t resizeCount; - size_t collisionsCount; }; struct celix_string_hash_map { @@ -384,10 +382,7 @@ celix_status_t celix_hashMap_init( map->removedStringEntryCallback = NULL; map->removedStringKeyCallback = NULL; map->storeKeysWeakly = false; - - map->modificationsCount = 0; map->resizeCount = 0; - map->collisionsCount = 0; map->buckets = calloc(initialCapacity, sizeof(celix_hash_map_entry_t*)); return map->buckets == NULL ? CELIX_ENOMEM : CELIX_SUCCESS; @@ -720,7 +715,7 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter) void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; - iter->index += 1; + iter->index = iter->index == map->size ? map->size : iter->index + 1; //increment but not beyond size entry = celix_hashMap_nextEntry(map, entry); if (entry) { iter->_internal[1] = entry; @@ -736,7 +731,7 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; - iter->index += 1; + iter->index = iter->index == map->size ? map->size : iter->index + 1; //increment but not beyond size entry = celix_hashMap_nextEntry(map, entry); if (entry) { iter->_internal[1] = entry; diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index 76a4fdc17..eda302b3b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -78,7 +77,7 @@ struct celix_properties { #define MALLOC_BLOCK_SIZE 5 -static void celix_properties_parseLine(const char* line, celix_properties_t* props); +static celix_status_t celix_properties_parseLine(const char* line, celix_properties_t* props); properties_pt properties_create(void) { return celix_properties_create(); } @@ -385,28 +384,31 @@ celix_properties_t* celix_properties_load(const char* filename) { return props; } -static void celix_properties_parseLine(const char* line, celix_properties_t* props) { +static celix_status_t celix_properties_parseLine(const char* line, celix_properties_t* props) { int linePos; int outputPos; bool precedingCharIsBackslash = false; - bool isComment = false; char* output = NULL; int key_len = MALLOC_BLOCK_SIZE; int value_len = MALLOC_BLOCK_SIZE; linePos = 0; precedingCharIsBackslash = false; - isComment = false; output = NULL; outputPos = 0; // Ignore empty lines if (line[0] == '\n' && line[1] == '\0') { - return; + return CELIX_SUCCESS; + } + + celix_autofree char* key = calloc(1, key_len); + celix_autofree char* value = calloc(1, value_len); + if (!key || !value) { + celix_err_pushf("Cannot allocate memory for key or value. Got error %i", errno); + return CELIX_ENOMEM; } - char* key = calloc(1, key_len); - char* value = calloc(1, value_len); key[0] = '\0'; value[0] = '\0'; @@ -431,8 +433,8 @@ static void celix_properties_parseLine(const char* line, celix_properties_t* pro } else { if (line[linePos] == '#' || line[linePos] == '!') { if (outputPos == 0) { - isComment = true; - break; + // comment line, ignore + return CELIX_SUCCESS; } else { output[outputPos++] = line[linePos]; updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len); @@ -466,14 +468,7 @@ static void celix_properties_parseLine(const char* line, celix_properties_t* pro output[outputPos] = '\0'; } - if (!isComment) { - // printf("putting 'key'/'value' '%s'/'%s' in properties\n", utils_stringTrim(key), utils_stringTrim(value)); - celix_properties_set(props, celix_utils_trimInPlace(key), celix_utils_trimInPlace(value)); - } - if (key) { - free(key); - free(value); - } + return celix_properties_set(props, celix_utils_trimInPlace(key), celix_utils_trimInPlace(value)); } celix_properties_t* celix_properties_loadWithStream(FILE* file) { @@ -500,7 +495,7 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { return NULL; } - char* fileBuffer = malloc(fileSize + 1); + celix_autofree char* fileBuffer = malloc(fileSize + 1); if (fileBuffer == NULL) { celix_err_pushf("Cannot allocate memory for file buffer. Got error %i", errno); return NULL; @@ -508,17 +503,21 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { size_t rs = fread(fileBuffer, sizeof(char), fileSize, file); if (rs < fileSize) { - fprintf(stderr, "fread read only %zu bytes out of %zu\n", rs, fileSize); + celix_err_pushf("fread read only %zu bytes out of %zu\n", rs, fileSize); + return NULL; } fileBuffer[fileSize] = '\0'; // ensure a '\0' at the end of the fileBuffer char* savePtr = NULL; char* line = strtok_r(fileBuffer, "\n", &savePtr); while (line != NULL) { - celix_properties_parseLine(line, props); + celix_status_t status = celix_properties_parseLine(line, props); + if (status != CELIX_SUCCESS) { + celix_err_pushf("Failed to parse line '%s'", line); + return NULL; + } line = strtok_r(NULL, "\n", &savePtr); } - free(fileBuffer); return celix_steal_ptr(props); } @@ -535,7 +534,11 @@ celix_properties_t* celix_properties_loadFromString(const char* input) { char* saveLinePointer = NULL; line = strtok_r(in, "\n", &saveLinePointer); while (line != NULL) { - celix_properties_parseLine(line, props); + celix_status_t status = celix_properties_parseLine(line, props); + if (status != CELIX_SUCCESS) { + celix_err_pushf("Failed to parse line '%s'", line); + return NULL; + } line = strtok_r(NULL, "\n", &saveLinePointer); } return celix_steal_ptr(props); @@ -713,7 +716,7 @@ celix_properties_setEntry(celix_properties_t* properties, const char* key, const case CELIX_PROPERTIES_VALUE_TYPE_VERSION: return celix_properties_setVersion(properties, key, entry->typed.versionValue); default: // STRING - return celix_properties_set(properties, key, entry->value); + return celix_properties_set(properties, key, entry->typed.strValue); } } return CELIX_SUCCESS; // silently ignore NULL entry @@ -845,8 +848,8 @@ celix_status_t celix_properties_setVersionWithoutCopy(celix_properties_t* props, return celix_properties_createAndSetEntry(props, key, &prototype); } -int celix_properties_size(const celix_properties_t* properties) { - return (int)celix_stringHashMap_size(properties->map); +size_t celix_properties_size(const celix_properties_t* properties) { + return celix_stringHashMap_size(properties->map); } bool celix_properties_equals(const celix_properties_t* props1, const celix_properties_t* props2) { @@ -877,12 +880,14 @@ typedef struct { } celix_properties_iterator_internal_t; celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties) { - CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t)); + celix_properties_iterator_t iter; celix_properties_iterator_internal_t internalIter; + + CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(iter._data)); + internalIter.mapIter = celix_stringHashMap_begin(properties->map); internalIter.props = properties; - celix_properties_iterator_t iter; iter.index = 0; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { iter.key = NULL; @@ -947,7 +952,7 @@ celix_properties_statistics_t celix_properties_getStatistics(const celix_propert celix_properties_statistics_t stats; stats.sizeOfKeysAndStringValues = sizeOfKeysAndStringValues; - stats.averageSizeOfKeysAndStringValues = (double)sizeOfKeysAndStringValues / celix_properties_size(properties) * 2; + stats.averageSizeOfKeysAndStringValues = (double)sizeOfKeysAndStringValues / (double)celix_properties_size(properties) * 2; stats.fillStringOptimizationBufferPercentage = (double)properties->currentStringBufferIndex / CELIX_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE; stats.fillEntriesOptimizationBufferPercentage = (double)properties->currentEntriesBufferIndex / CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE; stats.mapStatistics = celix_stringHashMap_getStatistics(properties->map); From 492c4579ad4c221d35a0470bc37aa950534fb612 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 18 Nov 2023 15:19:01 +0100 Subject: [PATCH 59/63] Fix hash map iterator index tests and behaviour --- libs/utils/gtest/src/HashMapTestSuite.cc | 44 ++++++++++++++++++++---- libs/utils/src/celix_hash_map.c | 2 ++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 6ec34cc8a..070678df9 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -506,18 +506,18 @@ TEST_F(HashMapTestSuite, IterateStressCapacityAndLoadFactorTest) { celix_longHashMap_destroy(lMap); } -TEST_F(HashMapTestSuite, IterateWithRemoveTest) { +TEST_F(HashMapTestSuite, IterateWithRemoveAtIndex0Test) { auto* sMap = createStringHashMap(6); auto iter1 = celix_stringHashMap_begin(sMap); while (!celix_stringHashMapIterator_isEnd(&iter1)) { - if (iter1.index % 2 == 0) { + if (iter1.index == 0) { // note only removing entries where the iter key is even celix_stringHashMapIterator_remove(&iter1); } else { - celix_stringHashMapIterator_next(&iter1); + FAIL() << "Should not be reached, because index 0 is always removed"; } } - EXPECT_EQ(3, celix_stringHashMap_size(sMap)); + EXPECT_EQ(0, celix_stringHashMap_size(sMap)); EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); celix_stringHashMapIterator_next(&iter1); EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); @@ -530,16 +530,48 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) { // note only removing entries where the iter index is even celix_longHashMapIterator_remove(&iter2); } else { - celix_longHashMapIterator_next(&iter2); + FAIL() << "Should not be reached, because index 0 is always removed"; } } - EXPECT_EQ(3, celix_longHashMap_size(lMap)); + EXPECT_EQ(0, celix_longHashMap_size(lMap)); EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); celix_longHashMap_destroy(lMap); } +TEST_F(HashMapTestSuite, IterateWithRemoveAtIndex4Test) { + celix_autoptr(celix_string_hash_map_t) sMap = createStringHashMap(6); + auto iter1 = celix_stringHashMap_begin(sMap); + while (!celix_stringHashMapIterator_isEnd(&iter1)) { + if (iter1.index == 4) { + // note only removing entries where the iter key is even + celix_stringHashMapIterator_remove(&iter1); + } else { + celix_stringHashMapIterator_next(&iter1); + } + } + EXPECT_EQ(4, celix_stringHashMap_size(sMap)); + EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); + celix_stringHashMapIterator_next(&iter1); + EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); + + celix_autoptr(celix_long_hash_map_t) lMap = createLongHashMap(6); + auto iter2 = celix_longHashMap_begin(lMap); + while (!celix_longHashMapIterator_isEnd(&iter2)) { + if (iter2.index == 4) { + // note only removing entries where the iter index is even + celix_longHashMapIterator_remove(&iter2); + } else { + celix_longHashMapIterator_next(&iter2); + } + } + EXPECT_EQ(4, celix_longHashMap_size(lMap)); + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); + celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); +} + TEST_F(HashMapTestSuite, IterateEndTest) { auto* sMap1 = createStringHashMap(0); auto* sMap2 = createStringHashMap(6); diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index aa2dd5721..84b103af1 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -764,6 +764,7 @@ void celix_stringHashMapIterator_remove(celix_string_hash_map_iterator_t* iter) const char* key = entry->key.strKey; celix_stringHashMapIterator_next(iter); celix_hashMap_remove(map, key, 0); + iter->index -= 1; //decrement index, because of remove } void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter) { @@ -772,6 +773,7 @@ void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter) { long key = entry->key.longKey; celix_longHashMapIterator_next(iter); celix_hashMap_remove(map, NULL, key); + iter->index -= 1; //decrement index, because of remove } static int celix_hashMap_nrOfEntriesInBucket(const celix_hash_map_t* map, int bucketIndex) { From 03731d62296c2382b6fbbc3b69c42d4e758f83a1 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Sat, 18 Nov 2023 16:35:32 +0100 Subject: [PATCH 60/63] Refactor properties createEntry to use destroyEntry --- libs/utils/include/celix_long_hash_map.h | 13 +++--- libs/utils/include/celix_string_hash_map.h | 15 +++---- libs/utils/src/properties.c | 48 +++++++++++----------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 8e71d9a8b..3416351df 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -53,11 +53,12 @@ typedef struct celix_long_hash_map celix_long_hash_map_t; * @Brief long hash map iterator, which contains a hash map entry's keu and value. */ typedef struct celix_long_hash_map_iterator { - size_t index; //iterator index, starting at 0 - long key; - celix_hash_map_value_t value; + size_t index; /**< The index of the iterator. Starts at 0, ends at map size (so beyond the last element). When an + entry is removed, the index value is not changed. */ + long key; /**< The key of the hash map entry. */ + celix_hash_map_value_t value; /**< The value of the hash map entry. */ - void* _internal[2]; //internal opaque struct + void* _internal[2]; /**< internal opaque data, do not use */ } celix_long_hash_map_iterator_t; /** @@ -344,11 +345,11 @@ bool celix_longHashMapIterator_equals( * * Small example of how to use the celix_longHashMapIterator_remove function: * @code{.c} - * //remove all even entries + * //remove all entries except the first 4 entries from hash map * celix_long_hash_map_t* map = ... * celix_long_hash_map_iterator_t iter = celix_longHashMap_begin(map); * while (!celix_longHashMapIterator_isEnd(&iter)) { - * if (iter.index % 2 == 0) { + * if (iter.index >= 4) { * celix_longHashMapIterator_remove(&ter); * } else { * celix_longHashMapIterator_next(&iter); diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 186012d0b..56b204e05 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -49,14 +49,15 @@ extern "C" { typedef struct celix_string_hash_map celix_string_hash_map_t; /** - * @Brief long hash map iterator, which contains a hash map entry's keu and value. + * @Brief string hash map iterator, which contains a hash map entry's keu and value. */ typedef struct celix_string_hash_map_iterator { - size_t index; //iterator index, starting at 0 - const char* key; - celix_hash_map_value_t value; + size_t index; /**< The index of the iterator. Starts at 0, ends at map size (so beyond the last element). When an + entry is removed, the index value is not changed. */ + const char* key; /**< The key of the hash map entry. */ + celix_hash_map_value_t value; /**< The value of the hash map entry. */ - void* _internal[2]; //internal opaque struct + void* _internal[2]; /**< internal opaque data, do not use */ } celix_string_hash_map_iterator_t; /** @@ -379,11 +380,11 @@ bool celix_stringHashMapIterator_equals( * * Small example of how to use the celix_stringHashMapIterator_remove function: * @code{.c} - * //remove all even entries from hash map + * //remove all entries except the first 4 entries from hash map * celix_string_hash_map_t* map = ... * celix_string_hash_map_iterator_t iter = celix_stringHashMap_begin(map); * while (!celix_stringHashMapIterator_isEnd(&iter)) { - * if (iter.index % 2 == 0) { + * if (iter.index >= 4) { * celix_stringHashMapIterator_remove(&ter); * } else { * celix_stringHashMapIterator_next(&iter); diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index eda302b3b..a8e3063f0 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -239,6 +239,27 @@ static celix_properties_entry_t* celix_properties_createEntryWithNoCopy(celix_pr return entry; } +static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) { + celix_properties_freeString(properties, (char*)entry->value); + if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)entry->typed.versionValue); + } + + if (entry >= properties->entriesBuffer && + entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { + if (entry == (properties->entriesBuffer + properties->currentEntriesBufferIndex - 1)) { + // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex + properties->currentEntriesBufferIndex -= 1; + } else { + // entry is part of the properties entries buffer, but not the last entry -> nop + } + } else { + free(entry); + } +} + + + /** * Create entry and optionally use the short properties optimization buffers. * Only 1 of the types values (strValue, LongValue, etc) should be provided. @@ -257,35 +278,12 @@ static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t celix_status_t status = celix_properties_fillEntry(properties, entry, prototype); if (status != CELIX_SUCCESS) { celix_err_pushf("Cannot fill property entry"); - if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - celix_version_destroy((celix_version_t*)prototype->typed.versionValue); - } - if (entry >= properties->entriesBuffer && - entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { - // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex - properties->currentEntriesBufferIndex -= 1; - } else { - free(entry); - } - entry = NULL; + celix_properties_destroyEntry(properties, entry); + return NULL; } return entry; } -static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) { - celix_properties_freeString(properties, (char*)entry->value); - if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { - celix_version_destroy((celix_version_t*)entry->typed.versionValue); - } - - if (entry >= properties->entriesBuffer && - entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) { - // entry is part of the properties entries buffer -> nop. - } else { - free(entry); - } -} - /** * Create and add entry and optionally use the short properties optimization buffers. * The prototype is used to determine the type of the value. From caedd795072c71ef938ae2939f07c4ac7b78fb20 Mon Sep 17 00:00:00 2001 From: PengZheng Date: Sun, 19 Nov 2023 14:48:59 +0800 Subject: [PATCH 61/63] Avoid unnecessary strlen call and check error condition in celix_properties_storeEscapedString. --- libs/utils/gtest/src/PropertiesTestSuite.cc | 1 + libs/utils/src/properties.c | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index f77abf82a..d9b4e8a2e 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -677,6 +677,7 @@ TEST_F(PropertiesTestSuite, PropertiesNullArgumentsTest) { EXPECT_EQ(CELIX_SUCCESS, celix_properties_setDouble(nullptr, "key", 1.0)); EXPECT_EQ(CELIX_SUCCESS, celix_properties_setBool(nullptr, "key", true)); EXPECT_EQ(CELIX_SUCCESS, celix_properties_setVersion(nullptr, "key", nullptr)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setVersionWithoutCopy(nullptr, "key", nullptr)); celix_autoptr(celix_properties_t) copy = celix_properties_copy(nullptr); EXPECT_NE(nullptr, copy); } diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index a8e3063f0..acaf8535b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -547,11 +547,12 @@ celix_properties_t* celix_properties_loadFromString(const char* input) { */ static int celix_properties_storeEscapedString(FILE* file, const char* str) { int rc = 0; - for (int i = 0; i < strlen(str); i += 1) { + size_t len = strlen(str); + for (size_t i = 0; i < len && rc != EOF; i += 1) { if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') { rc = fputc('\\', file); if (rc == EOF) { - break; + continue; } } rc = fputc(str[i], file); From 78d7879927ec9f91c44b52a53b5a6bd18908dcc6 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 20 Nov 2023 18:44:33 +0100 Subject: [PATCH 62/63] Remove index field from hashmap and properties iterator --- libs/utils/gtest/src/HashMapTestSuite.cc | 78 ++------------------- libs/utils/gtest/src/PropertiesTestSuite.cc | 8 +-- libs/utils/include/celix/Properties.h | 2 +- libs/utils/include/celix_long_hash_map.h | 2 - libs/utils/include/celix_properties.h | 5 -- libs/utils/include/celix_string_hash_map.h | 2 - libs/utils/src/celix_hash_map.c | 6 -- libs/utils/src/properties.c | 3 - 8 files changed, 7 insertions(+), 99 deletions(-) diff --git a/libs/utils/gtest/src/HashMapTestSuite.cc b/libs/utils/gtest/src/HashMapTestSuite.cc index 070678df9..b6ec5c76b 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -428,7 +428,7 @@ TEST_F(HashMapTestSuite, IterateTest) { auto* sMap = createStringHashMap(2); size_t count = 0; CELIX_STRING_HASH_MAP_ITERATE(sMap, iter) { - EXPECT_EQ(count++, iter.index); + count++; if (iter.value.longValue == 0) { EXPECT_STREQ(iter.key, "key0"); } else if (iter.value.longValue == 1) { @@ -443,7 +443,7 @@ TEST_F(HashMapTestSuite, IterateTest) { auto* lMap = createLongHashMap(2); count = 0; CELIX_LONG_HASH_MAP_ITERATE(lMap, iter) { - EXPECT_EQ(count++, iter.index); + count++; if (iter.value.longValue == 0) { EXPECT_EQ(iter.key, 0); } else if (iter.value.longValue == 1) { @@ -461,7 +461,7 @@ TEST_F(HashMapTestSuite, IterateStressTest) { auto* sMap = createStringHashMap(testCount); EXPECT_EQ(testCount, celix_stringHashMap_size(sMap)); int count = 0; - CELIX_STRING_HASH_MAP_ITERATE(sMap, iter) { EXPECT_EQ(iter.index, count++); } + CELIX_STRING_HASH_MAP_ITERATE(sMap, iter) { count++; } EXPECT_EQ(testCount, count); testGetEntriesFromStringMap(sMap, 100); celix_stringHashMap_destroy(sMap); @@ -469,7 +469,7 @@ TEST_F(HashMapTestSuite, IterateStressTest) { auto* lMap = createLongHashMap(testCount); EXPECT_EQ(testCount, celix_longHashMap_size(lMap)); count = 0; - CELIX_LONG_HASH_MAP_ITERATE(lMap, iter) { EXPECT_EQ(iter.index, count++); } + CELIX_LONG_HASH_MAP_ITERATE(lMap, iter) { count++; } EXPECT_EQ(testCount, count); testGetEntriesFromLongMap(lMap, 100); celix_longHashMap_destroy(lMap); @@ -506,72 +506,6 @@ TEST_F(HashMapTestSuite, IterateStressCapacityAndLoadFactorTest) { celix_longHashMap_destroy(lMap); } -TEST_F(HashMapTestSuite, IterateWithRemoveAtIndex0Test) { - auto* sMap = createStringHashMap(6); - auto iter1 = celix_stringHashMap_begin(sMap); - while (!celix_stringHashMapIterator_isEnd(&iter1)) { - if (iter1.index == 0) { - // note only removing entries where the iter key is even - celix_stringHashMapIterator_remove(&iter1); - } else { - FAIL() << "Should not be reached, because index 0 is always removed"; - } - } - EXPECT_EQ(0, celix_stringHashMap_size(sMap)); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); - celix_stringHashMapIterator_next(&iter1); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); - celix_stringHashMap_destroy(sMap); - - auto* lMap = createLongHashMap(6); - auto iter2 = celix_longHashMap_begin(lMap); - while (!celix_longHashMapIterator_isEnd(&iter2)) { - if (iter2.index % 2 == 0) { - // note only removing entries where the iter index is even - celix_longHashMapIterator_remove(&iter2); - } else { - FAIL() << "Should not be reached, because index 0 is always removed"; - } - } - EXPECT_EQ(0, celix_longHashMap_size(lMap)); - EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); - celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing - EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); - celix_longHashMap_destroy(lMap); -} - -TEST_F(HashMapTestSuite, IterateWithRemoveAtIndex4Test) { - celix_autoptr(celix_string_hash_map_t) sMap = createStringHashMap(6); - auto iter1 = celix_stringHashMap_begin(sMap); - while (!celix_stringHashMapIterator_isEnd(&iter1)) { - if (iter1.index == 4) { - // note only removing entries where the iter key is even - celix_stringHashMapIterator_remove(&iter1); - } else { - celix_stringHashMapIterator_next(&iter1); - } - } - EXPECT_EQ(4, celix_stringHashMap_size(sMap)); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); - celix_stringHashMapIterator_next(&iter1); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); - - celix_autoptr(celix_long_hash_map_t) lMap = createLongHashMap(6); - auto iter2 = celix_longHashMap_begin(lMap); - while (!celix_longHashMapIterator_isEnd(&iter2)) { - if (iter2.index == 4) { - // note only removing entries where the iter index is even - celix_longHashMapIterator_remove(&iter2); - } else { - celix_longHashMapIterator_next(&iter2); - } - } - EXPECT_EQ(4, celix_longHashMap_size(lMap)); - EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); - celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing - EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2)); -} - TEST_F(HashMapTestSuite, IterateEndTest) { auto* sMap1 = createStringHashMap(0); auto* sMap2 = createStringHashMap(6); @@ -583,10 +517,6 @@ TEST_F(HashMapTestSuite, IterateEndTest) { auto lIter1 = celix_longHashMap_end(lMap1); auto lIter2 = celix_longHashMap_end(lMap2); - EXPECT_EQ(sIter1.index, 0); - EXPECT_EQ(sIter2.index, 6); - EXPECT_EQ(lIter1.index, 0); - EXPECT_EQ(lIter2.index, 6); EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter1)); EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter2)); EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1)); diff --git a/libs/utils/gtest/src/PropertiesTestSuite.cc b/libs/utils/gtest/src/PropertiesTestSuite.cc index d9b4e8a2e..3e395908e 100644 --- a/libs/utils/gtest/src/PropertiesTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -550,21 +550,17 @@ TEST_F(PropertiesTestSuite, EndOfPropertiesTest) { auto* props = celix_properties_create(); celix_properties_set(props, "key1", "value1"); celix_properties_set(props, "key2", "value2"); - celix_properties_iterator_t endIter = celix_properties_end(props); - EXPECT_EQ(endIter.index, 2); EXPECT_TRUE(celix_propertiesIterator_isEnd(&endIter)); - celix_properties_destroy(props); } TEST_F(PropertiesTestSuite, EndOfEmptyPropertiesTest) { auto* props = celix_properties_create(); - celix_properties_iterator_t endIter = celix_properties_end(props); - EXPECT_EQ(endIter.index, 0); EXPECT_TRUE(celix_propertiesIterator_isEnd(&endIter)); - + celix_properties_iterator_t beginIter = celix_properties_begin(props); + EXPECT_TRUE(celix_propertiesIterator_isEnd(&beginIter)); //empty properties: begin == end celix_properties_destroy(props); } diff --git a/libs/utils/include/celix/Properties.h b/libs/utils/include/celix/Properties.h index 163b83ca9..eed6f510d 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -83,7 +83,7 @@ namespace celix { } } - celix_properties_iterator_t iter{.index = 0, .key = nullptr, .entry = {}, ._data = {}}; + celix_properties_iterator_t iter{.key = nullptr, .entry = {}, ._data = {}}; }; diff --git a/libs/utils/include/celix_long_hash_map.h b/libs/utils/include/celix_long_hash_map.h index 3416351df..73bcf0b94 100644 --- a/libs/utils/include/celix_long_hash_map.h +++ b/libs/utils/include/celix_long_hash_map.h @@ -53,8 +53,6 @@ typedef struct celix_long_hash_map celix_long_hash_map_t; * @Brief long hash map iterator, which contains a hash map entry's keu and value. */ typedef struct celix_long_hash_map_iterator { - size_t index; /**< The index of the iterator. Starts at 0, ends at map size (so beyond the last element). When an - entry is removed, the index value is not changed. */ long key; /**< The key of the hash map entry. */ celix_hash_map_value_t value; /**< The value of the hash map entry. */ diff --git a/libs/utils/include/celix_properties.h b/libs/utils/include/celix_properties.h index e94e2d9d9..594c0eb73 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -93,11 +93,6 @@ typedef struct celix_properties_entry { * @brief Represents an iterator for iterating over the entries in a celix_properties_t object. */ typedef struct celix_properties_iterator { - /** - * @brief The index of the current iterator. - */ - size_t index; - /** * @brief The current key. */ diff --git a/libs/utils/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 56b204e05..1f0972ce0 100644 --- a/libs/utils/include/celix_string_hash_map.h +++ b/libs/utils/include/celix_string_hash_map.h @@ -52,8 +52,6 @@ typedef struct celix_string_hash_map celix_string_hash_map_t; * @Brief string hash map iterator, which contains a hash map entry's keu and value. */ typedef struct celix_string_hash_map_iterator { - size_t index; /**< The index of the iterator. Starts at 0, ends at map size (so beyond the last element). When an - entry is removed, the index value is not changed. */ const char* key; /**< The key of the hash map entry. */ celix_hash_map_value_t value; /**< The value of the hash map entry. */ diff --git a/libs/utils/src/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 84b103af1..bf36c3093 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -688,7 +688,6 @@ celix_string_hash_map_iterator_t celix_stringHashMap_end(const celix_string_hash celix_string_hash_map_iterator_t iter; iter._internal[0] = (void*)&map->genericMap; iter._internal[1] = NULL; - iter.index = map->genericMap.size; iter.key = ""; memset(&iter.value, 0, sizeof(iter.value)); return iter; @@ -698,7 +697,6 @@ celix_long_hash_map_iterator_t celix_longHashMap_end(const celix_long_hash_map_t celix_long_hash_map_iterator_t iter; iter._internal[0] = (void*)&map->genericMap; iter._internal[1] = NULL; - iter.index = map->genericMap.size; iter.key = 0L; memset(&iter.value, 0, sizeof(iter.value)); return iter; @@ -715,7 +713,6 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter) void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; - iter->index = iter->index == map->size ? map->size : iter->index + 1; //increment but not beyond size entry = celix_hashMap_nextEntry(map, entry); if (entry) { iter->_internal[1] = entry; @@ -731,7 +728,6 @@ void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) { void celix_longHashMapIterator_next(celix_long_hash_map_iterator_t* iter) { const celix_hash_map_t* map = iter->_internal[0]; celix_hash_map_entry_t *entry = iter->_internal[1]; - iter->index = iter->index == map->size ? map->size : iter->index + 1; //increment but not beyond size entry = celix_hashMap_nextEntry(map, entry); if (entry) { iter->_internal[1] = entry; @@ -764,7 +760,6 @@ void celix_stringHashMapIterator_remove(celix_string_hash_map_iterator_t* iter) const char* key = entry->key.strKey; celix_stringHashMapIterator_next(iter); celix_hashMap_remove(map, key, 0); - iter->index -= 1; //decrement index, because of remove } void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter) { @@ -773,7 +768,6 @@ void celix_longHashMapIterator_remove(celix_long_hash_map_iterator_t* iter) { long key = entry->key.longKey; celix_longHashMapIterator_next(iter); celix_hashMap_remove(map, NULL, key); - iter->index -= 1; //decrement index, because of remove } static int celix_hashMap_nrOfEntriesInBucket(const celix_hash_map_t* map, int bucketIndex) { diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index acaf8535b..c477c5b8b 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -887,7 +887,6 @@ celix_properties_iterator_t celix_properties_begin(const celix_properties_t* pro internalIter.mapIter = celix_stringHashMap_begin(properties->map); internalIter.props = properties; - iter.index = 0; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { iter.key = NULL; memset(&iter.entry, 0, sizeof(iter.entry)); @@ -908,7 +907,6 @@ celix_properties_iterator_t celix_properties_end(const celix_properties_t* prope celix_properties_iterator_t iter; memset(&iter, 0, sizeof(iter)); - iter.index = internalIter.mapIter.index; memcpy(iter._data, &internalIter, sizeof(internalIter)); return iter; } @@ -918,7 +916,6 @@ void celix_propertiesIterator_next(celix_properties_iterator_t* iter) { memcpy(&internalIter, iter->_data, sizeof(internalIter)); celix_stringHashMapIterator_next(&internalIter.mapIter); memcpy(iter->_data, &internalIter, sizeof(internalIter)); - iter->index = internalIter.mapIter.index; if (celix_stringHashMapIterator_isEnd(&internalIter.mapIter)) { iter->key = NULL; memset(&iter->entry, 0, sizeof(iter->entry)); From 95460e98acbceb145bef06a10f45457f0a069d57 Mon Sep 17 00:00:00 2001 From: Pepijn Noltes Date: Mon, 20 Nov 2023 19:00:18 +0100 Subject: [PATCH 63/63] Add ftell check and error injection test for properties --- .../gtest/src/PropertiesErrorInjectionTestSuite.cc | 10 ++++++++++ libs/utils/src/properties.c | 8 +++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 468b45f32..970578611 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -46,6 +46,7 @@ class PropertiesErrorInjectionTestSuite : public ::testing::Test { celix_ei_expect_fopen(nullptr, 0, nullptr); celix_ei_expect_fputc(nullptr, 0, 0); celix_ei_expect_fseek(nullptr, 0, 0); + celix_ei_expect_ftell(nullptr, 0, 0); celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); celix_ei_expect_celix_stringHashMap_put(nullptr, 0, 0); } @@ -225,6 +226,15 @@ TEST_F(PropertiesErrorInjectionTestSuite, LoadFailureTest) { ASSERT_EQ(1, celix_err_getErrorCount()); celix_err_resetErrors(); + // When a ftell error injection is set for celix_properties_loadWithStream + celix_ei_expect_ftell((void*)celix_properties_loadWithStream, 0, -1); + // Then the celix_properties_loadWithStream call fails + props = celix_properties_loadWithStream(memStream); + ASSERT_EQ(nullptr, props); + // And a celix err msg is set + ASSERT_EQ(1, celix_err_getErrorCount()); + celix_err_resetErrors(); + // When a malloc error injection is set for celix_properties_loadWithStream celix_ei_expect_malloc((void*)celix_properties_loadWithStream, 0, nullptr); // Then the celix_properties_loadWithStream call fails diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index c477c5b8b..8d3a2dfac 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -486,7 +486,13 @@ celix_properties_t* celix_properties_loadWithStream(FILE* file) { celix_err_pushf("Cannot seek to end of file. Got error %i", errno); return NULL; } - size_t fileSize = ftell(file); + + long fileSize = ftell(file); + if (fileSize < 0) { + celix_err_pushf("Cannot get file size. Got error %i", errno); + return NULL; + } + rc = fseek(file, 0, SEEK_SET); if (rc != 0) { celix_err_pushf("Cannot seek to start of file. Got error %i", errno);