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 + diff --git a/CHANGES.md b/CHANGES.md index e638870f4..1b712a700 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,6 +48,15 @@ limitations under the License. - The `OSGI_FRAMEWORK_UUID` ("org.osgi.framework.uuid") config property, replacement is `CELIX_FRAMEWORK_UUID`. - Removed support for bundle activator symbols without a `celix_` prefix. - Removed service property constant `CELIX_FRAMEWORK_SERVICE_PID`. +- Support and usage of "service.lang" service property is removed. +- pubsub_serializer.h is removed and no longer supported. Use pubsub_message_serialization_service.h instead. +- C Properties are no longer a direct typedef of `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 + +- Basic type support for value in celix Properties. # Noteworthy Changes for 2.4.0 (2023-09-27) 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_websocket/src/pubsub_websocket_admin.c b/bundles/pubsub/pubsub_admin_websocket/src/pubsub_websocket_admin.c index ee48d2769..41b970461 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 d048ef75f..f4730a8a9 100644 --- a/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c +++ b/bundles/pubsub/pubsub_discovery/src/pubsub_discovery_impl.c @@ -558,10 +558,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; - PROPERTIES_FOR_EACH((celix_properties_t*)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 7b78cea07..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 @@ -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); @@ -76,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; @@ -101,19 +98,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/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c b/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c index 422fc0cf9..214eb1d8b 100644 --- a/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c +++ b/bundles/pubsub/pubsub_spi/src/pubsub_endpoint.c @@ -44,9 +44,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 c2c97d7ff..2c8d32217 100644 --- a/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c +++ b/bundles/remote_services/discovery_common/src/endpoint_descriptor_writer.c @@ -139,20 +139,15 @@ 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); - + 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, propertyName); + xmlTextWriterWriteAttribute(writer->writer, NAME, (const xmlChar*)iter.key); - if (strcmp(CELIX_FRAMEWORK_SERVICE_NAME, (char*) propertyName) == 0) { + if (strcmp(CELIX_FRAMEWORK_SERVICE_NAME, (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 { @@ -162,7 +157,6 @@ static celix_status_t endpointDescriptorWriter_writeEndpoint(endpoint_descriptor xmlTextWriterEndElement(writer->writer); } - hashMapIterator_destroy(iter); xmlTextWriterEndElement(writer->writer); } 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 1dcbc3a35..f0cf81eb1 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,9 @@ 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, ""); + while (!celix_propertiesIterator_isEnd(propIter)) { + key = propIter->key; + val = propIter->entry.value; if (key) { DNSServiceErrorType err = TXTRecordSetValue(txtRecord, key, strlen(val), val); if (err != kDNSServiceErr_NoError) { @@ -354,9 +352,11 @@ static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroc return false; } if (splitTxtRecord && TXTRecordGetLength(txtRecord) >= maxTxtLen - UINT8_MAX) { + celix_propertiesIterator_next(propIter); break; } } + celix_propertiesIterator_next(propIter); } return true; } @@ -374,11 +374,11 @@ 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}; - 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); @@ -411,7 +411,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); @@ -541,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 +} 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 4e6c5e180..e8c34e3ec 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 @@ -745,10 +745,7 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic } } - hash_map_entry_pt entry = hashMap_getEntry(endpointProperties, (void *) CELIX_FRAMEWORK_SERVICE_ID); - - char* key = hashMapEntry_getKey(entry); - char *serviceId = (char *) hashMap_remove(endpointProperties, (void *) CELIX_FRAMEWORK_SERVICE_ID); + const char* serviceId = celix_properties_get(endpointProperties, CELIX_FRAMEWORK_SERVICE_ID, "-1"); const char *uuid = NULL; char buf[512]; @@ -775,12 +772,9 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic } 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)); + CELIX_PROPERTIES_ITERATE(props, iter) { + celix_properties_set(endpointProperties, iter.key, iter.entry.value); } - hashMapIterator_destroy(propIter); } *endpoint = calloc(1, sizeof(**endpoint)); @@ -794,8 +788,6 @@ static celix_status_t remoteServiceAdmin_createEndpointDescription(remote_servic (*endpoint)->properties = endpointProperties; } - free(key); - free(serviceId); free(keys); return status; @@ -1002,14 +994,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/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 72a8d1a53..c93ac4955 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,29 +231,26 @@ 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*) CELIX_FRAMEWORK_SERVICE_NAME) != 0 - && strcmp(additionalPropKey,(char*) CELIX_FRAMEWORK_SERVICE_ID) != 0) { + CELIX_PROPERTIES_ITERATE(additionalProperties, additionalPropIter) { + if (strcmp(additionalPropIter.key,(char*) CELIX_FRAMEWORK_SERVICE_NAME) != 0 + && strcmp(additionalPropIter.key,(char*) CELIX_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); } } } - 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 4ff46e0f8..5c91fefa7 100644 --- a/bundles/remote_services/topology_manager/src/topology_manager.c +++ b/bundles/remote_services/topology_manager/src/topology_manager.c @@ -399,8 +399,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 *) CELIX_FRAMEWORK_SERVICE_NAME); - char* name = (char *) hashMapEntry_getValue(entry); + const char* name = celix_properties_get(endpoint->properties, CELIX_FRAMEWORK_SERVICE_NAME, ""); // 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 f479a128c..2636da9f8 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/bundles/shell/shell/src/query_command.c b/bundles/shell/shell/src/query_command.c index b7f20fee0..13d6fab74 100644 --- a/bundles/shell/shell/src/query_command.c +++ b/bundles/shell/shell/src/query_command.c @@ -97,10 +97,8 @@ static void queryCommand_callback(void *handle, const celix_bundle_t *bnd) { if (data->opts->verbose) { 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/conanfile.py b/conanfile.py index f1d242112..f8288bca6 100644 --- a/conanfile.py +++ b/conanfile.py @@ -102,9 +102,15 @@ 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", } for comp in _celix_defaults.keys(): @@ -113,6 +119,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 +137,10 @@ 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_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") def package_id(self): del self.info.options.build_all 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/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/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/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 6f192714c..d965ef54f 100644 --- a/libs/framework/include/celix/Trackers.h +++ b/libs/framework/include/celix/Trackers.h @@ -403,7 +403,7 @@ namespace celix { long svcId = celix_properties_getAsLong(cProps, CELIX_FRAMEWORK_SERVICE_ID, -1L); long svcRanking = celix_properties_getAsLong(cProps, CELIX_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 bc24f4012..45519d0d1 100644 --- a/libs/framework/include/celix/UseServiceBuilder.h +++ b/libs/framework/include/celix/UseServiceBuilder.h @@ -159,10 +159,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 428f22b1b..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)); } @@ -210,7 +208,6 @@ inline celix::dm::DependencyManagerInfo DependencyManager::getInfo() const { } } - inline std::vector DependencyManager::getInfos() const { std::vector result{}; auto* cInfos = celix_dependencyManager_createInfos(cDependencyManager()); diff --git a/libs/framework/include/celix/dm/ServiceDependency_Impl.h b/libs/framework/include/celix/dm/ServiceDependency_Impl.h index 6bb03d2d0..7712bd835 100644 --- a/libs/framework/include/celix/dm/ServiceDependency_Impl.h +++ b/libs/framework/include/celix/dm/ServiceDependency_Impl.h @@ -230,20 +230,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; } @@ -489,19 +477,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; } @@ -524,7 +500,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"); @@ -541,7 +517,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/framework/src/service_reference.c b/libs/framework/src/service_reference.c index bce59c59e..21d1a2829 100644 --- a/libs/framework/src/service_reference.c +++ b/libs/framework/src/service_reference.c @@ -197,23 +197,28 @@ 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; + } + + *size = (unsigned int)celix_properties_size(props); + *keys = malloc((*size) * 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/rcm/src/celix_capability.c b/libs/rcm/src/celix_capability.c index 93aeaf5b2..bcea63bcf 100644 --- a/libs/rcm/src/celix_capability.c +++ b/libs/rcm/src/celix_capability.c @@ -72,53 +72,20 @@ 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; - 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)) { - equals = false; - break; - } - } - 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)) { - 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) { 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 +119,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 +129,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..d3ac3d9f7 100644 --- a/libs/rcm/src/celix_requirement.c +++ b/libs/rcm/src/celix_requirement.c @@ -79,55 +79,20 @@ 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 - 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; - } - } - 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; - } - } - return equals; + 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) { 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 +134,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/CMakeLists.txt b/libs/utils/CMakeLists.txt index a502a77db..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) @@ -91,11 +92,18 @@ 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_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_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) install(DIRECTORY include/ @@ -120,8 +128,10 @@ 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 src include_deprecated ) target_link_libraries(utils_cut PUBLIC ${UTILS_PUBLIC_DEPS} ${UTILS_PRIVATE_DEPS}) @@ -147,41 +157,19 @@ if (UTILS) target_include_directories(linked_list_test PRIVATE include_deprecated) target_link_libraries(linked_list_test utils_cut 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 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 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 utils_cut pthread) - - - if (EI_TESTS) - 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 utils_cut Celix::malloc_ei Celix::utils_ei pthread) - 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) + 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/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..5b0d9b219 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()}; }; @@ -153,7 +152,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 +209,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, 100000) -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..134a77dae 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,16 @@ static void StringHashmapBenchmark_findEntryFromCelixProperties(benchmark::State } } state.SetItemsProcessed(state.iterations()); + + auto stats = celix_properties_getStatistics(benchmark.celixProperties); + 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) { @@ -264,19 +280,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::kNanosecond) \ + ->RangeMultiplier(10)->Range(10, 100000) -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/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 3c38ad419..000000000 --- a/libs/utils/error_injector/celix_hash_map/include/celix_hash_map_ei.h +++ /dev/null @@ -1,34 +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*); - -#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 4edbc3810..000000000 --- a/libs/utils/error_injector/celix_hash_map/src/celix_hash_map_ei.cc +++ /dev/null @@ -1,29 +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(); -} -} 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_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/error_injector/celix_hash_map/CMakeLists.txt b/libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt similarity index 58% rename from libs/utils/error_injector/celix_hash_map/CMakeLists.txt rename to libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt index 25f87d49e..651b7afa0 100644 --- a/libs/utils/error_injector/celix_hash_map/CMakeLists.txt +++ b/libs/utils/error_injector/celix_string_hash_map/CMakeLists.txt @@ -15,11 +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 -) -add_library(Celix::hash_map_ei ALIAS hash_map_ei) + LINKER:--wrap,celix_stringHashMap_createWithOptions + 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/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 f2c9b0529..bfb9a5531 100644 --- a/libs/utils/gtest/CMakeLists.txt +++ b/libs/utils/gtest/CMakeLists.txt @@ -19,6 +19,7 @@ add_executable(test_utils src/CxxUtilsTestSuite.cc src/CxxPropertiesTestSuite.cc src/CxxFilterTestSuite.cc + src/CxxVersionTestSuite.cc src/LogTestSuite.cc src/LogUtilsTestSuite.cc src/VersionRangeTestSuite.cc @@ -27,6 +28,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 @@ -37,9 +40,10 @@ target_link_libraries(test_utils PRIVATE utils_cut Celix::utils GTest::gtest GTe 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) 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 ) @@ -93,6 +97,8 @@ if (EI_TESTS) src/ArrayListErrorInjectionTestSuite.cc src/ErrErrorInjectionTestSuite.cc src/PropertiesErrorInjectionTestSuite.cc + src/VersionErrorInjectionTestSuite.cc + src/HashMapErrorInjectionTestSuite.cc ) target_link_libraries(test_utils_with_ei PRIVATE Celix::zip_ei @@ -104,7 +110,10 @@ if (EI_TESTS) Celix::ifaddrs_ei Celix::threads_ei Celix::malloc_ei - Celix::hmap_ei + 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/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/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/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/CxxPropertiesTestSuite.cc b/libs/utils/gtest/src/CxxPropertiesTestSuite.cc index a66e433d7..f041a187f 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()); @@ -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++; @@ -59,7 +66,23 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) { EXPECT_EQ(5, count); } -TEST_F(CxxPropertiesTestSuite, testCopy) { +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; + } + + 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, CopyTest) { celix::Properties props{}; props["key1"] = "value1"; @@ -74,17 +97,111 @@ 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"); 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)); celix_properties_destroy(props); } + +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; + 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 + 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); + 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); + 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) { + 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); +} + +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_TRUE(loadedProps == props); + + 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")); + } +} diff --git a/libs/utils/gtest/src/CxxVersionTestSuite.cc b/libs/utils/gtest/src/CxxVersionTestSuite.cc new file mode 100644 index 000000000..53276b247 --- /dev/null +++ b/libs/utils/gtest/src/CxxVersionTestSuite.cc @@ -0,0 +1,87 @@ +/* + * 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 "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()); +} + +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/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/HashMapErrorInjectionTestSuite.cc b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc new file mode 100644 index 000000000..ddeb9ef5d --- /dev/null +++ b/libs/utils/gtest/src/HashMapErrorInjectionTestSuite.cc @@ -0,0 +1,110 @@ +/* + * 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); + celix_err_resetErrors(); + } +}; + +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.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 + 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 eec57f988..b6ec5c76b 100644 --- a/libs/utils/gtest/src/HashMapTestSuite.cc +++ b/libs/utils/gtest/src/HashMapTestSuite.cc @@ -19,14 +19,16 @@ #include -#include "celix_utils.h" -#include "celix_string_hash_map.h" +#include "celix_hash_map_private.h" #include "celix_long_hash_map.h" -#include +#include "celix_string_hash_map.h" +#include "celix_hash_map_internal.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 +48,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 +57,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 +68,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 +83,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 +92,25 @@ 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: + + + 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{}; }; @@ -190,12 +204,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); @@ -221,51 +235,54 @@ 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; - 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); - 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); 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); - 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); } - TEST_F(HashMapTestSuite, HashMapClearTests) { auto* sMap = createStringHashMap(3); EXPECT_EQ(celix_stringHashMap_size(sMap), 3); @@ -280,54 +297,37 @@ 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) { - 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"; } @@ -335,7 +335,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()); @@ -366,7 +366,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)); @@ -380,30 +380,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); } @@ -424,12 +424,11 @@ TEST_F(HashMapTestSuite, GetWithFallbackValues) { celix_longHashMap_destroy(lMap); } - 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) { @@ -444,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) { @@ -462,19 +461,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) { 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) { count++; } EXPECT_EQ(testCount, count); testGetEntriesFromLongMap(lMap, 100); celix_longHashMap_destroy(lMap); @@ -482,77 +477,201 @@ 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); 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.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); 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, 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_TRUE(celix_stringHashMapIterator_isEnd(&sIter1)); + EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter2)); + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1)); + EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter2)); + + celix_stringHashMap_destroy(sMap1); + celix_stringHashMap_destroy(sMap2); + celix_longHashMap_destroy(lMap1); + celix_longHashMap_destroy(lMap2); } -TEST_F(HashMapTestSuite, IterateWithRemoveTest) { - auto* sMap = createStringHashMap(6); - 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 - celix_stringHashMapIterator_remove(&iter1); - } else { - celix_stringHashMapIterator_next(&iter1); - } - } - EXPECT_EQ(3, celix_stringHashMap_size(sMap)); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); - celix_stringHashMapIterator_next(&iter1); - EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&iter1)); +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); +} - 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 { - celix_longHashMapIterator_next(&iter2); - } - } - EXPECT_EQ(3, 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, 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); } -TEST_F(HashMapTestSuite, StringHashMapCleanup) { +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_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); +} + +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)); + 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); + 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)); + 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); + 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)); +} + +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/PropertiesErrorInjectionTestSuite.cc b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc index 13f1f6b65..970578611 100644 --- a/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc +++ b/libs/utils/gtest/src/PropertiesErrorInjectionTestSuite.cc @@ -17,22 +17,296 @@ * under the License. * */ -#include "celix_properties.h" -#include "hmap_ei.h" #include +#include "celix/Properties.h" +#include "celix_cleanup.h" +#include "celix_err.h" +#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: + public: PropertiesErrorInjectionTestSuite() = default; ~PropertiesErrorInjectionTestSuite() override { - celix_ei_expect_hashMap_create(nullptr, 0, nullptr); + celix_err_resetErrors(); + 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); + 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); + } + + /** + * Fills the optimization cache of the given properties object to ensure the next added entries will need + * allocation. + */ + void fillOptimizationCache(celix_properties_t* props) { + 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", index++); + written += strlen(key) + strlen(val) + 2; + celix_properties_set(props, key, val); + } } }; +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 + + //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); - celix_ei_expect_hashMap_create((void *) &celix_properties_create, 0, nullptr); + 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)); -} \ No newline at end of file + 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{}; + celix_ei_expect_celix_stringHashMap_createWithOptions((void*)celix_properties_create, 0, nullptr); + ASSERT_THROW(celix::Properties{cxxProp}, std::bad_alloc); +} + +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(); + fillOptimizationCache(props); + + // 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); + + // 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 + 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 (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 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 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 + 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(); + + //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) { + // 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(); +} + +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 new file mode 100644 index 000000000..3e395908e --- /dev/null +++ b/libs/utils/gtest/src/PropertiesTestSuite.cc @@ -0,0 +1,723 @@ +/* + * 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_properties.h" +#include "celix_properties_internal.h" +#include "celix_utils.h" +#include "properties.h" + +using ::testing::MatchesRegex; + +class PropertiesTestSuite : public ::testing::Test { +public: + PropertiesTestSuite() { + celix_err_resetErrors(); + } + + void printStats(const celix_properties_statistics_t* stats) { + printf("Properties statistics:\n"); + 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); + } +}; + + +TEST_F(PropertiesTestSuite, CreateTest) { + auto* properties = celix_properties_create(); + EXPECT_TRUE(properties); + + celix_properties_destroy(properties); +} + +TEST_F(PropertiesTestSuite, LoadTest) { + char propertiesFile[] = "resources-test/properties.txt"; + 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, nullptr); + EXPECT_STREQ("b", valueA); + + const char keyNiceA[] = "nice_a"; + 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, nullptr); + EXPECT_STREQ("c \t d", valueB); + + 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"; + 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_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) { + celix_properties_t *props = celix_properties_create(); + celix_properties_set(props, "t1", "42"); + celix_properties_set(props, "t2", "-42"); + celix_properties_set(props, "t3", ""); + 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); + EXPECT_EQ(42, v); + + v = celix_properties_getAsLong(props, "t2", -1); + EXPECT_EQ(-42, v); + + v = celix_properties_getAsLong(props, "t3", -1); + EXPECT_EQ(-1, v); + + v = celix_properties_getAsLong(props, "t4", -1); + EXPECT_EQ(-1, v); + + v = celix_properties_getAsLong(props, "t5", -1); + EXPECT_EQ(-1, v); + + v = celix_properties_getAsLong(props, "non-existing", -1); + EXPECT_EQ(-1, v); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, GetSetTest) { + auto* properties = celix_properties_create(); + char keyA[] = "x"; + char keyB[] = "y"; + char keyC[] = "z"; + char *keyD = strndup("a", 1); + char valueA[] = "1"; + char valueB[] = "2"; + char valueC[] = "3"; + char *valueD = strndup("4", 1); + celix_properties_set(properties, keyA, valueA); + celix_properties_set(properties, keyB, valueB); + celix_properties_setWithoutCopy(properties, keyD, valueD); + + 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_F(PropertiesTestSuite, GetSetWithNullTest) { + 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); //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); +} + + +TEST_F(PropertiesTestSuite, SetUnsetTest) { + 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); + 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); + 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); +} + +TEST_F(PropertiesTestSuite, GetLongTest) { + auto* properties = celix_properties_create(); + + celix_properties_set(properties, "a", "2"); + celix_properties_set(properties, "b", "-10032"); + celix_properties_set(properties, "c", ""); + celix_properties_set(properties, "d", "garbage"); + + long a = celix_properties_getAsLong(properties, "a", -1L); + long b = celix_properties_getAsLong(properties, "b", -1L); + long c = celix_properties_getAsLong(properties, "c", -1L); + long d = celix_properties_getAsLong(properties, "d", -1L); + long e = celix_properties_getAsLong(properties, "e", -1L); + + 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); + EXPECT_EQ(3L, a); + EXPECT_EQ(-4L, b); + + celix_properties_destroy(properties); +} + +TEST_F(PropertiesTestSuite, GetAsDoubleTest) { + auto* properties = celix_properties_create(); + + celix_properties_set(properties, "a", "2"); + 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", ""); + 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(); + + celix_properties_set(properties, "a", "true"); + celix_properties_set(properties, "b", "false"); + celix_properties_set(properties, "c", " true "); + celix_properties_set(properties, "d", "garbage"); + + bool a = celix_properties_getAsBool(properties, "a", false); + bool b = celix_properties_getAsBool(properties, "b", true); + bool c = celix_properties_getAsBool(properties, "c", false); + bool d = celix_properties_getAsBool(properties, "d", true); + bool e = celix_properties_getAsBool(properties, "e", false); + bool f = celix_properties_getAsBool(properties, "f", true); + + 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); + EXPECT_EQ(true, a); + EXPECT_EQ(false, b); + + celix_properties_destroy(properties); +} + +TEST_F(PropertiesTestSuite, GetFillTest) { + 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, GetSetOverwrite) { + auto* props = celix_properties_create(); + auto* version = celix_version_createEmptyVersion(); + + { + char* key = celix_utils_strdup("key"); + celix_properties_set(props, key, "str1"); + free(key); + } + EXPECT_STREQ("str1", celix_properties_get(props, "key", "")); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_set(props, "key", "str2")); + EXPECT_STREQ("str2", celix_properties_get(props, "key", "")); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setLong(props, "key", 1)); + EXPECT_EQ(1, celix_properties_getAsLong(props, "key", -1L)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setDouble(props, "key", 2.0)); + EXPECT_EQ(2.0, celix_properties_getAsLong(props, "key", -2.0)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setBool(props, "key", false)); + EXPECT_EQ(false, celix_properties_getAsBool(props, "key", true)); + 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"); + + celix_properties_destroy(props); +} + + + +TEST_F(PropertiesTestSuite, SizeAndIteratorTest) { + celix_properties_t *props = celix_properties_create(); + EXPECT_EQ(0, celix_properties_size(props)); + celix_properties_set(props, "a", "1"); + celix_properties_set(props, "b", "2"); + EXPECT_EQ(2, celix_properties_size(props)); + celix_properties_set(props, "c", " 3 "); + celix_properties_set(props, "d", "4"); + EXPECT_EQ(4, celix_properties_size(props)); + + int count = 0; + CELIX_PROPERTIES_ITERATE(props, entry) { + EXPECT_NE(entry.key, nullptr); + count++; + } + EXPECT_EQ(4, count); + + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, GetTypeAndCopyTest) { + 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_create(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")); + 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")); + + 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) { + 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_create(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(nullptr, entry); + + celix_version_destroy(version); + celix_properties_destroy(props); +} + +TEST_F(PropertiesTestSuite, IteratorNextTest) { + 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.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, IterateOverPropertiesTest) { + auto* 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.key, "key"), nullptr); + + // Inner loop to test nested iteration + CELIX_PROPERTIES_ITERATE(props, innerIter) { + innerCount++; + EXPECT_NE(strstr(innerIter.key, "key"), nullptr); + } + } + // Check that both entries were iterated over + EXPECT_EQ(outerCount, 2); + 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, GetVersionTest) { + auto* properties = celix_properties_create(); + auto* emptyVersion = celix_version_createEmptyVersion(); + + // Test getting a version property + 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)); + 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"); + 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); + + // Test setting without copy + 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); + 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); +} + +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_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_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); +} + +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, 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(); + 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); +} + +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); +} + +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)); + EXPECT_TRUE(celix_properties_equals(prop1, prop1)); + + 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 +} + +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)); + EXPECT_EQ(CELIX_SUCCESS, celix_properties_setVersionWithoutCopy(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()); +} + +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()); + } + + auto stats = celix_properties_getStatistics(props); + 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/gtest/src/VersionErrorInjectionTestSuite.cc b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.cc new file mode 100644 index 000000000..019c87263 --- /dev/null +++ b/libs/utils/gtest/src/VersionErrorInjectionTestSuite.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 + +#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 { +public: + VersionErrorInjectionTestSuite() { + 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, 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((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/gtest/src/VersionRangeTestSuite.cc b/libs/utils/gtest/src/VersionRangeTestSuite.cc index e51a53481..2c44a79fb 100644 --- a/libs/utils/gtest/src/VersionRangeTestSuite.cc +++ b/libs/utils/gtest/src/VersionRangeTestSuite.cc @@ -51,7 +51,7 @@ TEST_F(VersionRangeTestSuite, cleanup) { 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); @@ -70,7 +70,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; @@ -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); diff --git a/libs/utils/gtest/src/VersionTestSuite.cc b/libs/utils/gtest/src/VersionTestSuite.cc new file mode 100644 index 000000000..5e908fd85 --- /dev/null +++ b/libs/utils/gtest/src/VersionTestSuite.cc @@ -0,0 +1,321 @@ +/* + * 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" + +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; + + version = celix_version_create(1, 2, 3, "abc"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3, "abc"); + celix_version_destroy(version); + + EXPECT_EQ(nullptr, celix_version_create(-1, -2, -3, "abc")); + EXPECT_EQ(nullptr, celix_version_create(-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); + 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; + 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, CopyTest) { + auto* version = celix_version_create(1, 2, 3, "abc"); + auto* copy = celix_version_copy(version); + EXPECT_NE(nullptr, version); + EXPECT_NE(nullptr, 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); + expectVersion(copy, 0, 0, 0, ""); + celix_version_destroy(copy); +} + +TEST_F(VersionTestSuite, CreateFromStringTest) { + auto* version = celix_version_createVersionFromString("1"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 0, 0); + version_destroy(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")); + + version = celix_version_createVersionFromString("1.2"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 0); + version_destroy(version); + + version = celix_version_createVersionFromString("1.2.3"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3); + version_destroy(version); + + version = celix_version_createVersionFromString("1.2.3.abc"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3, "abc"); + version_destroy(version); + + version = celix_version_createVersionFromString("1.2.3.abc_xyz"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3, "abc_xyz"); + version_destroy(version); + + + version = celix_version_createVersionFromString("1.2.3.abc-xyz"); + EXPECT_TRUE(version != nullptr); + expectVersion(version, 1, 2, 3, "abc-xyz"); + version_destroy(version); + + + //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); + + EXPECT_EQ(CELIX_ILLEGAL_ARGUMENT, version_createVersionFromString("a", &version)); +} + +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); +} + +TEST_F(VersionTestSuite, CompareTest) { + 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 against 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, CompareToMajorMinorTest) { + 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)); + + 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, ToStringTest) { + 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, SemanticCompatibilityTest) { + 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, 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, ""); + 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, FillStringTest) { + // Create a version object + auto* version = celix_version_create(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); +} 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 907101589..eed6f510d 100644 --- a/libs/utils/include/celix/Properties.h +++ b/libs/utils/include/celix/Properties.h @@ -19,93 +19,120 @@ #pragma once +#include #include #include #include #include +#include #include "celix_properties.h" #include "celix_utils.h" +#include "celix/Version.h" +#include "celix/IOException.h" namespace celix { /** * @brief A iterator for celix::Properties. */ - class PropertiesIterator { + class ConstPropertiesIterator { public: - explicit PropertiesIterator(celix_properties_t* props) { - iter = celix_propertiesIterator_construct(props); - next(); + explicit ConstPropertiesIterator(const celix_properties_t* props) { + iter = celix_properties_begin(props); + setFields(); } - explicit PropertiesIterator(const celix_properties_t* props) { - iter = celix_propertiesIterator_construct(props); - next(); + explicit ConstPropertiesIterator(celix_properties_iterator_t _iter) { + iter = _iter; + setFields(); } - PropertiesIterator& operator++() { + ConstPropertiesIterator& operator++() { next(); return *this; } - PropertiesIterator& operator*() { + const ConstPropertiesIterator& operator*() const { return *this; } - 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; - } + 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); } 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 { - moveToEnd(); - } - } - - void moveToEnd() { - end = true; - first = {}; - second = {}; + celix_propertiesIterator_next(&iter); + setFields(); } std::string first{}; std::string second{}; private: - celix_properties_iterator_t iter{nullptr, nullptr, nullptr, 0, 0}; - bool end{false}; + void setFields() { + if (celix_propertiesIterator_isEnd(&iter)) { + first = {}; + second = {}; + } else { + first = iter.key; + second = iter.entry.value; + } + } + + celix_properties_iterator_t iter{.key = nullptr, .entry = {}, ._data = {}}; }; /** * @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 { 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 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 = PropertiesIterator; + using const_iterator = ConstPropertiesIterator; //note currently only a const iterator is supported. + + /** + * @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: @@ -145,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) { @@ -167,11 +193,34 @@ namespace celix { } /** - * @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) { + static const Properties wrap(const celix_properties_t* wrapProps) { auto* cp = const_cast(wrapProps); - return std::shared_ptr{new Properties{cp}}; + 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}; + } + + /** + * @brief Wrap C properties and take ownership -> dtor will destroy C properties. + */ + static Properties own(celix_properties_t* wrapProps) { + 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}; } /** @@ -198,36 +247,41 @@ 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 */ const_iterator begin() const noexcept { - return PropertiesIterator{cProps.get()}; + return ConstPropertiesIterator{cProps.get()}; } /** * @brief end iterator */ const_iterator end() const noexcept { - auto iter = PropertiesIterator{cProps.get()}; - iter.moveToEnd(); - return iter; + return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } /** * @brief constant begin iterator */ const_iterator cbegin() const noexcept { - return PropertiesIterator{cProps.get()}; + return ConstPropertiesIterator{cProps.get()}; } /** * @brief constant end iterator */ const_iterator cend() const noexcept { - auto iter = PropertiesIterator{cProps.get()}; - iter.moveToEnd(); - return iter; + return ConstPropertiesIterator{celix_properties_end(cProps.get())}; } /** @@ -239,108 +293,295 @@ 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. */ 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. */ 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. */ bool getAsBool(const std::string &key, bool defaultValue) const { return celix_properties_getAsBool(cProps.get(), key.c_str(), defaultValue); } /** - * @brief Sets a property + * @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. */ - void set(const std::string& key, const std::string& value) { - celix_properties_set(cProps.get(), key.c_str(), value.c_str()); + 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{ + celix_version_getMajor(cVersion), + celix_version_getMinor(cVersion), + celix_version_getMicro(cVersion), + celix_version_getQualifier(cVersion)}; + celix_version_destroy(cVersion); + return version; + } + return defaultValue; } /** - * @brief Sets a bool 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. + * @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.c_str(), 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.c_str()); + throwIfEnomem(status); } /** - * @brief Sets a const char* 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.c_str(), 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 T property. Will use (std::) to_string to convert the value to string. + * @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. + * + * @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::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); + } + + /** + * @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. + * @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) { + auto status = celix_properties_setVersion(cProps.get(), key.data(), value.getCVersion()); + throwIfEnomem(status); + } + + /** + * @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. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. + */ + 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); + } + + /** + * @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. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. + */ + 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); } /** - * @brief Sets a String property. + * @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. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. */ template - typename std::enable_if<::celix::Properties::IsString::value>::type + typename std::enable_if<::celix::Properties::IsFloatingPoint::value>::type set(const std::string& key, T&& value) { - celix_properties_set(cProps.get(), key.c_str(), value.c_str()); + auto status = celix_properties_setDouble(cProps.get(), key.data(), value); + throwIfEnomem(status); + } + + /** + * @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. + * @throws std::bad_alloc If a ENOMEM error occurs while setting the property. + */ + void set(const std::string& key, const celix_version_t* value) { + auto status = celix_properties_setVersion(cProps.get(), key.data(), value); + throwIfEnomem(status); } /** - * @brief Returns the nr of properties. + * @brief Returns the number of properties in the Properties object. */ std::size_t size() const { return celix_properties_size(cProps.get()); } /** - * @brief Converts the properties a (new) std::string, std::string map. + * @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. + */ + ValueType getType(const std::string& key) { + return getAndConvertType(cProps, key.data()); + } + + /** + * @brief Convert the properties a (new) std::string, std::string map. */ 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; } /** - * @brief Converts the properties a (new) std::string, std::string unordered map. + * @brief Convert the properties a (new) std::string, std::string unordered map. */ 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; } -#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. + * @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 (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. */ - operator std::map() const { - return convertToMap(); + 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. + * @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. + */ + static celix::Properties load(const std::string& path) { return loadFrom(path.data()); } + private: - explicit Properties(celix_properties_t* props) : cProps{props, [](celix_properties_t*) { /*nop*/ }} {} + 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) { + 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; + } + } + + 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; }; @@ -350,4 +591,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 +} diff --git a/libs/utils/include/celix/Version.h b/libs/utils/include/celix/Version.h new file mode 100644 index 000000000..8fb19449b --- /dev/null +++ b/libs/utils/include/celix/Version.h @@ -0,0 +1,183 @@ +/* + * 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 { + + /** + * @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())} {} + + /** + * @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. + */ + 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())} {} + + /** + * @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; + } + + /** + * @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}; + } + + /** + * @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(); + } + + /** + * @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()); + } + + /** + * @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()); + } + + /** + * @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()); + } + + /** + * @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; + } + 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*/}}, + qualifier{celix_version_getQualifier(cVersion.get())} {} + + std::shared_ptr cVersion; + std::string qualifier; //cached qualifier of the const char* from celix_version_getQualifier + }; +} + +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 { + 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 1215db2fb..73bcf0b94 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 @@ -52,11 +53,10 @@ 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; + 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; /** @@ -71,6 +71,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; @@ -91,6 +94,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; @@ -105,21 +113,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). + * If 0 is provided, the hash map load factor will be 5 (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 @@ -131,7 +139,7 @@ typedef struct celix_long_hash_map_create_options { .removedCallbackData = NULL, \ .removedCallback = NULL, \ .initialCapacity = 0, \ - .loadFactor = 0 \ + .maxLoadFactor = 0 \ } #endif @@ -163,51 +171,64 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_long_hash_map_t, celix_longHashMap_destr CELIX_UTILS_EXPORT 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. + * 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. + * @param[in] value The value to store with the key + * @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, an 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, an 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, an 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); @@ -265,49 +286,68 @@ 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 Create and return a hash map iterator for the beginning of the hash 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. + * + * @param[in] map The map to get the iterator for. + * @return An iterator pointing to the first element in the map. */ CELIX_UTILS_EXPORT 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. + * + * @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); + +/** * - * @note the end iterator should not be used to retrieve a key of value. + * @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. */ CELIX_UTILS_EXPORT 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. */ CELIX_UTILS_EXPORT 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 - * 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 + * @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 * * Small example of how to use the celix_longHashMapIterator_remove function: - * @code - * //remove all even entries + * @code{.c} + * //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); @@ -317,6 +357,23 @@ CELIX_UTILS_EXPORT void celix_longHashMapIterator_next(celix_long_hash_map_itera */ CELIX_UTILS_EXPORT 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 862df87b8..594c0eb73 100644 --- a/libs/utils/include/celix_properties.h +++ b/libs/utils/include/celix_properties.h @@ -17,86 +17,510 @@ * 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. + * + * Supported property value types include: + * - string (char*) + * - long + * - double + * - bool + * - celix_version_t* + */ + #ifndef CELIX_PROPERTIES_H_ #define CELIX_PROPERTIES_H_ #include -#include #include "celix_cleanup.h" #include "celix_compiler.h" #include "celix_errno.h" #include "celix_utils_export.h" +#include "celix_version.h" #ifdef __cplusplus 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. + * + * @note Not thread safe. + */ +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_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 */ + + 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. */ + 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; + +/** + * @brief Represents an iterator for iterating over the entries in a celix_properties_t object. + */ typedef struct celix_properties_iterator { - //private data - void* _data1; - void* _data2; - void* _data3; - int _data4; - int _data5; -} celix_properties_iterator_t; + /** + * @brief The current key. + */ + const char* key; + /** + * @brief The current value entry. + */ + celix_properties_entry_t entry; -/********************************************************************************************************************** - ********************************************************************************************************************** - * Updated API - ********************************************************************************************************************** - **********************************************************************************************************************/ + /** + * @brief Private data used to implement the iterator. + */ + char _data[56]; +} celix_properties_iterator_t; -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void); +/** + * @brief Create a new empty property set. + * + * If the return status is an error, an error message is logged to celix_err. + * + * @return A new empty property set. + */ +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(); -CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties); +/** + * @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. + */ +CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t* properties); CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_properties_t, celix_properties_destroy) -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char *filename); +/** + * @brief Load properties from a file. + * + * 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. + * @retval NULL If an error occurred (e.g. file not found). + */ +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filename); + +/** + * @brief Load properties from a stream. + * + * 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. + * @retval NULL If an error occurred (e.g. invalid format). + */ +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream); + +/** + * @brief Load properties from a string. + * + * 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. + * @retval NULL If an error occurred (e.g. invalid format). + */ +CELIX_UTILS_EXPORT 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. + * + * 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. + * @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. + */ +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. + * + * @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 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); + +/** + * @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. + * @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); + +/** + * @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. + * @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); + +/** + * @brief Set the value of a property. + * + * 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] 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 + * 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, + const char* value); + +/** + * @brief Set the value of a property without copying the value string. + * + * 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 + * 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 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, + char* value); + +/** + * @brief Get the value of a property as 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. + * 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); + +/** + * @brief Set the value of a property to a long integer. + * + * 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] 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 + * 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); + +/** + * @brief Get the value of a property as a boolean. + * + * @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 + * 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 celix_properties_t* celix_properties_loadWithStream(FILE *stream); +/** + * @brief Set the value of a property to a boolean. + * + * 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] 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 + * 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); -CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input); +/** + * @brief Set the value of a property to a double. + * + * 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] 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 + * 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, + double val); -CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header); +/** + * @brief Get the value of a property as 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 + * 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 const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue); +/** + * @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, 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] 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 + * 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, + const celix_version_t* version); -CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value); +/** + * @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. + * 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] 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); -CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value); +/** + * @brief Get the Celix version value of a property. + * + * This function does not convert a string property value to a Celix version automatically. + * + * @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. + */ +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 void celix_properties_unset(celix_properties_t *properties, const char *key); +/** + * @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_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_properties_t* celix_properties_copy(const celix_properties_t *properties); +/** + * @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 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. + */ +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); -CELIX_UTILS_EXPORT long celix_properties_getAsLong(const celix_properties_t *props, const char *key, long defaultValue); -CELIX_UTILS_EXPORT void celix_properties_setLong(celix_properties_t *props, const char *key, long value); +/** + * @brief Make a copy of a properties set. + * + * 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. + */ +CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t* properties); -CELIX_UTILS_EXPORT bool celix_properties_getAsBool(const celix_properties_t *props, const char *key, bool defaultValue); -CELIX_UTILS_EXPORT void celix_properties_setBool(celix_properties_t *props, const char *key, bool val); +/** + * @brief Get the number of properties in a property set. + * + * @param[in] properties The property set to get the size of. + * @return The number of properties in the property set. + */ +CELIX_UTILS_EXPORT size_t 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); -CELIX_UTILS_EXPORT void celix_properties_setDouble(celix_properties_t *props, const char *key, double val); -CELIX_UTILS_EXPORT double celix_properties_getAsDouble(const celix_properties_t *props, const char *key, double defaultValue); +/** + * @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. + */ +CELIX_UTILS_EXPORT celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties); -CELIX_UTILS_EXPORT int celix_properties_size(const celix_properties_t *properties); +/** + * @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_UTILS_EXPORT celix_properties_iterator_t celix_properties_end(const celix_properties_t* properties); -CELIX_UTILS_EXPORT celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties); -CELIX_UTILS_EXPORT bool celix_propertiesIterator_hasNext(celix_properties_iterator_t *iter); -CELIX_UTILS_EXPORT const char* celix_propertiesIterator_nextKey(celix_properties_iterator_t *iter); -CELIX_UTILS_EXPORT celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter); +/** + * @brief Advance the iterator to the next entry. + * + * @param[in, out] iter The iterator. + */ +CELIX_UTILS_EXPORT void celix_propertiesIterator_next(celix_properties_iterator_t* iter); -#define CELIX_PROPERTIES_FOR_EACH(props, key) _CELIX_PROPERTIES_FOR_EACH(CELIX_UNIQUE_ID(iter), props, key) +/** + * @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. + * + * @param[in] iter The iterator. + * @return true if the iterator is at an end position, false otherwise. + */ +CELIX_UTILS_EXPORT bool celix_propertiesIterator_isEnd(const celix_properties_iterator_t* iter); -#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);) +/** + * @brief Determine whether two iterators are equal. + * + * @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. + */ +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. + * + * 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[in] props The properties object to iterate over. + * @param[in] 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(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/include/celix_string_hash_map.h b/libs/utils/include/celix_string_hash_map.h index 9cd70fd71..1f0972ce0 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 @@ -48,14 +49,13 @@ 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; + 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; /** @@ -70,11 +70,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. */ @@ -90,14 +93,40 @@ 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 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 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. + * + * 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. */ @@ -114,21 +143,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 @@ -139,9 +168,10 @@ typedef struct celix_string_hash_map_create_options { .simpleRemovedCallback = NULL, \ .removedCallbackData = NULL, \ .removedCallback = NULL, \ + .removedKeyCallback = NULL, \ .storeKeysWeakly = false, \ .initialCapacity = 0, \ - .loadFactor = 0 \ + .maxLoadFactor = 0 \ } #endif @@ -173,43 +203,55 @@ CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_string_hash_map_t, celix_stringHashMap_d CELIX_UTILS_EXPORT 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. + * 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. + * @param[in] value The value to store with the key. + * @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, 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. * @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, 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. - * @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, 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. * @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. @@ -274,48 +316,73 @@ 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 Create and return a hash map iterator for the beginning of the hash 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. + * + * 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. + */ +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. + * + * @param[in] map The map to get the iterator for. + * @return An iterator pointing to the first element in the map. */ CELIX_UTILS_EXPORT 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. + * + * @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); + +/** * - * @note the end iterator should not be used to retrieve a key of value. + * @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. */ CELIX_UTILS_EXPORT 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. */ CELIX_UTILS_EXPORT void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter); /** - * @brief Marco to loop over all the entries of a string hash map. + * @brief Compares two celix_string_hash_map_iterator_t objects for equality. * - * Small example of how to use the iterate macro: - * @code - * 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 + * 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. */ -#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 * * Small example of how to use the celix_stringHashMapIterator_remove function: - * @code - * //remove all even entries from hash map + * @code{.c} + * //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); @@ -325,6 +392,24 @@ CELIX_UTILS_EXPORT void celix_stringHashMapIterator_next(celix_string_hash_map_i */ CELIX_UTILS_EXPORT 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/include/celix_utils.h b/libs/utils/include/celix_utils.h index 707637ebd..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 @@ -101,8 +106,11 @@ CELIX_UTILS_EXPORT bool celix_utils_containsWhitespace(const char* s); /** * @brief Returns a trimmed string. * - * The trim will remove any 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. */ CELIX_UTILS_EXPORT char* celix_utils_trim(const char* string); /** diff --git a/libs/utils/include/celix_version.h b/libs/utils/include/celix_version.h index 9da0b8009..34e67c25e 100644 --- a/libs/utils/include/celix_version.h +++ b/libs/utils/include/celix_version.h @@ -20,47 +20,61 @@ #ifndef CELIX_CELIX_VERSION_H #define CELIX_CELIX_VERSION_H -#ifdef __cplusplus -extern "C" { -#endif - #include +#include #include "celix_cleanup.h" #include "celix_utils_export.h" +#ifdef __cplusplus +extern "C" { +#endif + /** - * The definition of the celix_version_t* abstract data type. + * @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. */ 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. + * + * If the return is NULL, an error message is logged to celix_err. * - * @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 + * @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_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); + CELIX_UTILS_EXPORT void celix_version_destroy(celix_version_t* version); CELIX_DEFINE_AUTOPTR_CLEANUP_FUNC(celix_version_t, celix_version_destroy) /** - * 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_UTILS_EXPORT 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. @@ -77,27 +91,50 @@ CELIX_UTILS_EXPORT celix_version_t* celix_version_copy(const celix_version_t* ve * * 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_UTILS_EXPORT celix_version_t* celix_version_createVersionFromString(const char *versionStr); /** - * The empty version "0.0.0". - * + * @brief Create empty version "0.0.0". */ CELIX_UTILS_EXPORT 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. + */ CELIX_UTILS_EXPORT 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. + */ CELIX_UTILS_EXPORT 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. + */ 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 an empty string ("") if no qualifier is present. + */ CELIX_UTILS_EXPORT 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 @@ -122,26 +159,38 @@ CELIX_UTILS_EXPORT const char* celix_version_getQualifier(const celix_version_t* CELIX_UTILS_EXPORT 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 */ CELIX_UTILS_EXPORT 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 * 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); /** - * 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. + */ +CELIX_UTILS_EXPORT 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 @@ -154,7 +203,7 @@ CELIX_UTILS_EXPORT char* celix_version_toString(const celix_version_t* version); CELIX_UTILS_EXPORT 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 @@ -168,7 +217,7 @@ CELIX_UTILS_EXPORT bool celix_version_isCompatible(const celix_version_t* user, CELIX_UTILS_EXPORT 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/include_deprecated/properties.h b/libs/utils/include_deprecated/properties.h index 05d4d5a89..d03f5f940 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"))); CELIX_UTILS_DEPRECATED_EXPORT celix_properties_t* properties_create(void); @@ -62,10 +62,11 @@ CELIX_UTILS_DEPRECATED_EXPORT void properties_unset(celix_properties_t *properti CELIX_UTILS_DEPRECATED_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/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..34df8eb12 --- /dev/null +++ b/libs/utils/include_internal/celix_properties_internal.h @@ -0,0 +1,54 @@ +/* +* 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 + +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 of the provided properties set. + */ +CELIX_UTILS_EXPORT celix_properties_statistics_t celix_properties_getStatistics(const celix_properties_t* properties); + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_PROPERTIES_INTERNAL_H diff --git a/libs/utils/private/test/properties_test.cpp b/libs/utils/private/test/properties_test.cpp deleted file mode 100644 index 64230ee94..000000000 --- a/libs/utils/private/test/properties_test.cpp +++ /dev/null @@ -1,270 +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. - */ -/** - * 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" - -extern "C" { -#include - -#include "properties.h" -#include "celix_properties.h" -} - -int main(int argc, char** argv) { - MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); - return RUN_ALL_TESTS(argc, argv); -} - -TEST_GROUP(properties) { - celix_properties_t *properties; - - void setup(void) { - } - - void teardown() { - mock().checkExpectations(); - mock().clear(); - } -}; - -TEST(properties, create) { - properties = celix_properties_create(); - CHECK(properties); - - celix_properties_destroy(properties); -} - -TEST(properties, load) { - char propertiesFile[] = "resources-test/properties.txt"; - properties = celix_properties_load(propertiesFile); - LONGS_EQUAL(4, hashMap_size(properties)); - - const char keyA[] = "a"; - const char *valueA = celix_properties_get(properties, keyA, NULL); - STRCMP_EQUAL("b", valueA); - - const char keyNiceA[] = "nice_a"; - const char *valueNiceA = celix_properties_get(properties, keyNiceA, NULL); - STRCMP_EQUAL("nice_b", valueNiceA); - - const char keyB[] = "b"; - const char *valueB = celix_properties_get(properties, keyB, NULL); - STRCMP_EQUAL("c \t d", valueB); - - celix_properties_destroy(properties); -} - -TEST(properties, asLong) { - celix_properties_t *props = celix_properties_create(); - 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, "t5", "bla"); - - long v = celix_properties_getAsLong(props, "t1", -1); - LONGS_EQUAL(42, v); - - v = celix_properties_getAsLong(props, "t2", -1); - LONGS_EQUAL(-42, v); - - v = celix_properties_getAsLong(props, "t3", -1); - LONGS_EQUAL(-1, v); - - v = celix_properties_getAsLong(props, "t4", -1); - LONGS_EQUAL(42, v); - - v = celix_properties_getAsLong(props, "t5", -1); - LONGS_EQUAL(-1, v); - - v = celix_properties_getAsLong(props, "non-existing", -1); - LONGS_EQUAL(-1, v); - - celix_properties_destroy(props); -} - -TEST(properties, store) { - char propertiesFile[] = "resources-test/properties_out.txt"; - 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_destroy(properties); -} - -TEST(properties, copy) { - char propertiesFile[] = "resources-test/properties.txt"; - properties = celix_properties_load(propertiesFile); - LONGS_EQUAL(4, hashMap_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 keyB[] = "b"; - STRCMP_EQUAL("c \t d", celix_properties_get(copy, keyB, NULL)); - - celix_properties_destroy(properties); - celix_properties_destroy(copy); -} - -TEST(properties, getSet) { - properties = celix_properties_create(); - char keyA[] = "x"; - char keyB[] = "y"; - char keyC[] = "z"; - char *keyD = strndup("a", 1); - char valueA[] = "1"; - char valueB[] = "2"; - char valueC[] = "3"; - char *valueD = strndup("4", 1); - celix_properties_set(properties, keyA, valueA); - 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)); - - celix_properties_destroy(properties); -} - -TEST(properties, setUnset) { - 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)); - - 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)); - celix_properties_destroy(properties); -} - -TEST(properties, longTest) { - properties = celix_properties_create(); - - celix_properties_set(properties, "a", "2"); - celix_properties_set(properties, "b", "-10032L"); - celix_properties_set(properties, "c", ""); - celix_properties_set(properties, "d", "garbage"); - - long a = celix_properties_getAsLong(properties, "a", -1L); - long b = celix_properties_getAsLong(properties, "b", -1L); - long c = celix_properties_getAsLong(properties, "c", -1L); - 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); - - 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); - - celix_properties_destroy(properties); -} - -TEST(properties, boolTest) { - properties = celix_properties_create(); - - celix_properties_set(properties, "a", "true"); - celix_properties_set(properties, "b", "false"); - celix_properties_set(properties, "c", " true "); - celix_properties_set(properties, "d", "garbage"); - - bool a = celix_properties_getAsBool(properties, "a", false); - bool b = celix_properties_getAsBool(properties, "b", true); - bool c = celix_properties_getAsBool(properties, "c", false); - bool d = celix_properties_getAsBool(properties, "d", true); - 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); - - 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); - - celix_properties_destroy(properties); -} - -TEST(properties, sizeAndIteratorTest) { - celix_properties_t *props = celix_properties_create(); - CHECK_EQUAL(0, celix_properties_size(props)); - celix_properties_set(props, "a", "1"); - celix_properties_set(props, "b", "2"); - CHECK_EQUAL(2, celix_properties_size(props)); - celix_properties_set(props, "c", " 3 "); - celix_properties_set(props, "d", "4"); - CHECK_EQUAL(4, celix_properties_size(props)); - - int count = 0; - const char *_key = NULL; - CELIX_PROPERTIES_FOR_EACH(props, _key) { - count++; - } - CHECK_EQUAL(4, count); - - celix_properties_destroy(props); -} - -TEST(properties, autocleanup) { - celix_autoptr(celix_properties_t) props = celix_properties_create(); -} diff --git a/libs/utils/private/test/version_ei_test.cc b/libs/utils/private/test/version_ei_test.cc deleted file mode 100644 index 66606ae45..000000000 --- a/libs/utils/private/test/version_ei_test.cc +++ /dev/null @@ -1,56 +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 "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" - -int main(int argc, char** argv) { - MemoryLeakWarningPlugin::turnOffNewDeleteOverloads(); - return RUN_ALL_TESTS(argc, argv); -} - -TEST_GROUP(version_ei) { - - void setup(void) { - celix_ei_expect_calloc(nullptr, 0, nullptr); - celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); - } - - void teardown(void) { - celix_ei_expect_calloc(nullptr, 0, nullptr); - celix_ei_expect_celix_utils_strdup(nullptr, 0, nullptr); - } -}; - -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); - POINTERS_EQUAL(nullptr, version); - - celix_ei_expect_celix_utils_strdup(CELIX_EI_UNKNOWN_CALLER, 0, nullptr); - version = celix_version_createVersion(2, 2, 0, nullptr); - POINTERS_EQUAL(nullptr, version); -} diff --git a/libs/utils/private/test/version_test.cpp b/libs/utils/private/test/version_test.cpp deleted file mode 100644 index e167576bc..000000000 --- a/libs/utils/private/test/version_test.cpp +++ /dev/null @@ -1,421 +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); - status = version_isCompatible(NULL, provider, &isCompatible); - CHECK(isCompatible == false); - LONGS_EQUAL(CELIX_SUCCESS, status); - status = version_isCompatible(provider, NULL, &isCompatible); - CHECK(isCompatible == false); - LONGS_EQUAL(CELIX_SUCCESS, status); - - 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/celix_hash_map.c b/libs/utils/src/celix_hash_map.c index 1ef9b75ee..bf36c3093 100644 --- a/libs/utils/src/celix_hash_map.c +++ b/libs/utils/src/celix_hash_map.c @@ -19,7 +19,8 @@ #include "celix_string_hash_map.h" #include "celix_long_hash_map.h" -#include "celix_utils.h" +#include "celix_hash_map_private.h" +#include "celix_hash_map_internal.h" #include #include @@ -27,34 +28,22 @@ #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; +#include "celix_utils.h" +#include "celix_err.h" +#include "celix_stdlib_cleanup.h" -typedef enum celix_hash_map_key_type { - CELIX_HASH_MAP_STRING_KEY, - CELIX_HASH_MAP_LONG_KEY -} celix_hash_map_key_type_e; +#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 -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; @@ -62,21 +51,22 @@ 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 - double loadFactor; + double maxLoadFactor; 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); 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; + + //statistics + size_t resizeCount; +}; struct celix_string_hash_map { celix_hash_map_t genericMap; @@ -86,42 +76,55 @@ 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); -} +// hash1 (original) +//static unsigned int celix_longHashMap_hash(long key) { +// return key ^ (key >> (sizeof(key)*8/2)); +//} -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) { - 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_equals(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); +/** + * @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 +1) / (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; @@ -163,27 +166,39 @@ 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; +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 - 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; @@ -194,41 +209,56 @@ static void 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; } -#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*)); +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->removedLongEntryCallback) { + map->removedLongEntryCallback(map->removedCallbackData, removedEntry->key.longKey, removedEntry->value); + } else if (map->removedStringEntryCallback) { + map->removedStringEntryCallback(map->removedCallbackData, removedEntry->key.strKey, removedEntry->value); + } +} - 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); - } +static void celix_hashMap_destroyRemovedKey(celix_hash_map_t* map, char* removedKey) { + if (map->removedStringKeyCallback) { + map->removedStringKeyCallback(map->removedCallbackData, removedKey); } - free(map->buckets); - map->buckets = newTable; - map->bucketsSize = newCapacity; + 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); + free(removedEntry); } -#endif -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, 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) { + celix_err_push("Cannot allocate memory for hash map entry"); + return CELIX_ENOMEM; + } + 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; @@ -236,135 +266,126 @@ static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, con memcpy(&newEntry->value, value, sizeof(*value)); newEntry->next = entry; map->buckets[bucketIndex] = newEntry; - if (map->size++ >= celix_hashMap_threshold(map)) { - celix_hashMap_resize(map, 2 * map->bucketsSize); - } + map->size += 1; + + 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) { +/** + * @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_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; - } - memcpy(&entry->value, value, sizeof(*value)); - return true; - } - } - celix_hashMap_addEntry(map, hash, &key, value, index); - if (replacedValueOut != NULL) { - memset(replacedValueOut, 0, sizeof(*replacedValueOut)); + + celix_hash_map_entry_t* entryFound = celix_hashMap_getEntry(map, strKey, longKey); + if (entryFound) { + //replace value + celix_hashMap_callRemovedCallback(map, entryFound); + memcpy(&entryFound->value, value, sizeof(*value)); + return CELIX_SUCCESS; } - return false; + + //new entry + celix_status_t status = celix_hashMap_addEntry(map, &key, value); + 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) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(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); } -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) { 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 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; + 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 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) { celix_hash_map_value_t value; + memset(&value, 0, sizeof(value)); value.boolValue = v; - 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); + return celix_hashMap_putValue(map, strKey, longKey, &value); } +/** + * @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 (strKey) { + removedKey = (char*)removedEntry->key.strKey; + } celix_hashMap_destroyRemovedEntry(map, removedEntry); + if (removedKey) { + celix_hashMap_destroyRemovedKey(map, removedKey); + } return true; } 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, - 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; - map->buckets = calloc(initialCapacity, sizeof(celix_hash_map_entry_t*)); + double maxLoadFactor) { + map->maxLoadFactor = maxLoadFactor; 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; map->removedCallbackData = NULL; - map->removedLongKeyCallback = NULL; + map->removedLongEntryCallback = NULL; + map->removedStringEntryCallback = NULL; map->removedStringKeyCallback = NULL; map->storeKeysWeakly = false; + map->resizeCount = 0; + + 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) { @@ -373,7 +394,14 @@ 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; } @@ -416,15 +444,27 @@ 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)); - 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); + 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 : 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; + } + 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; + + return celix_steal_ptr(map); } celix_string_hash_map_t* celix_stringHashMap_create() { @@ -433,15 +473,27 @@ 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)); - 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); + 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 : 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; + } + 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; + + return celix_steal_ptr(map); } celix_long_hash_map_t* celix_longHashMap_create() { @@ -506,35 +558,35 @@ 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) { +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); } -void* celix_longHashMap_put(celix_long_hash_map_t* map, long key, void* 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); } -bool celix_stringHashMap_putLong(celix_string_hash_map_t* map, const char* key, long 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); } -bool celix_longHashMap_putLong(celix_long_hash_map_t* map, long key, long 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); } -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) { +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); } -bool celix_longHashMap_putBool(celix_long_hash_map_t* map, long key, bool 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); } @@ -562,6 +614,50 @@ 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 == 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)) { + 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; + } + } + return true; +} + +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); +} + 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)); @@ -588,6 +684,24 @@ 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; + iter._internal[0] = (void*)&map->genericMap; + iter._internal[1] = NULL; + 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; + iter._internal[0] = (void*)&map->genericMap; + iter._internal[1] = NULL; + iter.key = 0L; + memset(&iter.value, 0, sizeof(iter.value)); + 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,14 +714,14 @@ 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); - if (entry != NULL) { + if (entry) { iter->_internal[1] = entry; - iter->index += 1; 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 = ""; + memset(&iter->value, 0, sizeof(iter->value)); } } @@ -615,17 +729,31 @@ 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]; 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]; @@ -641,3 +769,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 new file mode 100644 index 000000000..4fb97a24f --- /dev/null +++ b/libs/utils/src/celix_hash_map_private.h @@ -0,0 +1,65 @@ +/** + *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_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 +#define CELIX_CELIX_HASH_MAP_PRIVATE_H + +#include "celix_errno.h" +#include "celix_hash_map_value.h" + +#ifdef __cplusplus +extern "C" { +#endif + +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 next allowed size. + */ +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, const celix_hash_map_key_t* key, const celix_hash_map_value_t* value); + +/** + * @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 maxLoadFactor); + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_HASH_MAP_PRIVATE_H diff --git a/libs/utils/src/celix_properties_private.h b/libs/utils/src/celix_properties_private.h new file mode 100644 index 000000000..209b2ef0c --- /dev/null +++ b/libs/utils/src/celix_properties_private.h @@ -0,0 +1,49 @@ +/* +* 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 + +/** + * @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); + + +#ifdef __cplusplus +} +#endif + +#endif // CELIX_CELIX_PROPERTIES_PRIVATE_H \ No newline at end of file diff --git a/libs/utils/src/celix_utils_private_constants.h.in b/libs/utils/src/celix_utils_private_constants.h.in new file mode 100644 index 000000000..eadb684db --- /dev/null +++ b/libs/utils/src/celix_utils_private_constants.h.in @@ -0,0 +1,42 @@ +/* + * 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_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) + * 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_UTILS_PRIVATE_CONSTANTS_H diff --git a/libs/utils/src/properties.c b/libs/utils/src/properties.c index d49870fa7..8d3a2dfac 100644 --- a/libs/utils/src/properties.c +++ b/libs/utils/src/properties.c @@ -17,57 +17,89 @@ * under the License. */ +#include "properties.h" +#include "celix_properties.h" +#include "celix_properties_private.h" +#include "celix_properties_internal.h" + +#include +#include +#include #include -#include #include -#include -#include +#include -#include "properties.h" #include "celix_build_assert.h" -#include "celix_properties.h" +#include "celix_err.h" +#include "celix_string_hash_map.h" #include "celix_utils.h" -#include "utils.h" -#include "hash_map_private.h" -#include -#include "hash_map.h" +#include "celix_stdlib_cleanup.h" +#include "celix_convert_utils.h" +#include "celix_utils_private_constants.h" -#define MALLOC_BLOCK_SIZE 5 +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 = ""; -static void parseLine(const char* line, celix_properties_t *props); +struct celix_properties { + celix_string_hash_map_t* map; -properties_pt properties_create(void) { - return celix_properties_create(); -} + /** + * 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 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]; -void properties_destroy(properties_pt properties) { - celix_properties_destroy(properties); -} + /** + * The current string buffer index. + */ + int currentStringBufferIndex; -properties_pt properties_load(const char* filename) { - return celix_properties_load(filename); -} + /** + * 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 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]; -properties_pt properties_loadWithStream(FILE *file) { - return celix_properties_loadWithStream(file); -} + /** + * The current string buffer index. + */ + int currentEntriesBufferIndex; +}; -properties_pt properties_loadFromString(const char *input){ - return celix_properties_loadFromString(input); -} +#define MALLOC_BLOCK_SIZE 5 +static celix_status_t celix_properties_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_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; + return copy == NULL ? CELIX_ENOMEM : CELIX_SUCCESS; } const char* properties_get(properties_pt properties, const char* key) { @@ -82,19 +114,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); @@ -103,67 +132,316 @@ static void updateBuffers(char **key, char ** value, char **output, int outputPo } } -static void parseLine(const char* line, celix_properties_t *props) { - int linePos = 0; +/** + * 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) { + if (str == NULL) { + return (char*)CELIX_PROPERTIES_EMPTY_STRVAL; + } + size_t len = strnlen(str, CELIX_UTILS_MAX_STRLEN) + 1; + size_t left = CELIX_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; +} +/** + * 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_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. + */ +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] = {0}; + *entry = *prototype; + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + 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) { + // 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) { + 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 (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { + 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->value = celix_properties_createString(properties, prototype->typed.strValue); + entry->typed.strValue = entry->value; + } + + if (entry->value == NULL) { + return CELIX_ENOMEM; + } + return CELIX_SUCCESS; +} + +/** + * Allocate entry and optionally use the short properties optimization entries buffer. + */ +celix_properties_entry_t* celix_properties_allocEntry(celix_properties_t* properties) { + celix_properties_entry_t* entry; + if (properties->currentEntriesBufferIndex < CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_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, + const char* strValue) { + celix_properties_entry_t* entry = celix_properties_allocEntry(properties); + if (entry == NULL) { + return NULL; + } + entry->value = strValue; + entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + entry->typed.strValue = strValue; + 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. + */ +static celix_properties_entry_t* celix_properties_createEntry(celix_properties_t* properties, + 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"); + return NULL; + } + + celix_status_t status = celix_properties_fillEntry(properties, entry, prototype); + if (status != CELIX_SUCCESS) { + celix_err_pushf("Cannot fill property entry"); + celix_properties_destroyEntry(properties, entry); + return NULL; + } + return entry; +} + +/** + * Create and add entry and optionally use the short properties optimization buffers. + * 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 celix_properties_entry_t* prototype) { + if (!properties) { + if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) { + celix_version_destroy((celix_version_t*)prototype->typed.versionValue); + } + return CELIX_SUCCESS; // silently ignore + } + + if (!key) { + 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, prototype); + 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; + } + } + + 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) { + 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_destroyEntry(properties, entry); +} + +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; + opts.storeKeysWeakly = true; + opts.initialCapacity = CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE; + 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; + if (props->map == NULL) { + free(props); + props = NULL; + } + } + 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) { + celix_err_pushf("Cannot open file '%s'", filename); + return NULL; + } + celix_properties_t* props = celix_properties_loadWithStream(file); + fclose(file); + return 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; - int outputPos = 0; - char *output = NULL; + 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 + // 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'; 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 { + // comment line, ignore + return CELIX_SUCCESS; + } 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; @@ -171,15 +449,13 @@ static void parseLine(const char* line, celix_properties_t *props) { } } } - } - 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); @@ -190,325 +466,497 @@ static void parseLine(const char* line, celix_properties_t *props) { 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); - } - if(value) { - free(value); - } - + return celix_properties_set(props, celix_utils_trimInPlace(key), celix_utils_trimInPlace(value)); } +celix_properties_t* celix_properties_loadWithStream(FILE* file) { + if (file == NULL) { + return NULL; + } -/********************************************************************************************************************** - ********************************************************************************************************************** - * Updated API - ********************************************************************************************************************** - **********************************************************************************************************************/ + celix_autoptr(celix_properties_t) props = celix_properties_create(); + if (!props) { + celix_err_push("Failed to create properties"); + return NULL; + } + 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; + } + long fileSize = ftell(file); + if (fileSize < 0) { + celix_err_pushf("Cannot get file size. Got error %i", errno); + return NULL; + } -celix_properties_t* celix_properties_create(void) { - return hashMap_create(utils_stringHash, utils_stringHash, utils_stringEquals, utils_stringEquals); -} + rc = fseek(file, 0, SEEK_SET); + if (rc != 0) { + celix_err_pushf("Cannot seek to start of file. Got error %i", errno); + return NULL; + } -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_autofree char* fileBuffer = malloc(fileSize + 1); + if (fileBuffer == NULL) { + celix_err_pushf("Cannot allocate memory for file buffer. Got error %i", errno); + return NULL; } -} -celix_properties_t* celix_properties_load(const char *filename) { - FILE *file = fopen(filename, "r"); - if (file == NULL) { + size_t rs = fread(fileBuffer, sizeof(char), fileSize, file); + if (rs < fileSize) { + celix_err_pushf("fread read only %zu bytes out of %zu\n", rs, fileSize); return NULL; } - celix_properties_t *props = celix_properties_loadWithStream(file); - fclose(file); - return props; -} + 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_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); + } -celix_properties_t* celix_properties_loadWithStream(FILE *file) { - celix_properties_t *props = NULL; + return celix_steal_ptr(props); +} - if (file != NULL ) { - char *saveptr; - char *filebuffer = NULL; - char *line = NULL; - ssize_t file_size = 0; +celix_properties_t* celix_properties_loadFromString(const char* input) { + 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; + } - props = celix_properties_create(); - fseek(file, 0, SEEK_END); - file_size = ftell(file); - fseek(file, 0, SEEK_SET); + char* line = NULL; + char* saveLinePointer = NULL; + line = strtok_r(in, "\n", &saveLinePointer); + while (line != NULL) { + 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); +} - 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); +/** + * @brief Store properties string to file and escape the characters '#', '!', '=' and ':' if encountered. + */ +static int celix_properties_storeEscapedString(FILE* file, const char* str) { + int rc = 0; + 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) { + continue; } } + rc = fputc(str[i], file); } - - return props; + return rc; } -celix_properties_t* celix_properties_loadFromString(const char *input) { - celix_properties_t *props = celix_properties_create(); +celix_status_t celix_properties_store(celix_properties_t* properties, const char* filename, const char* header) { + FILE* file = fopen(filename, "w+"); + + if (file == NULL) { + celix_err_pushf("Cannot open file '%s'", filename); + return CELIX_FILE_IO_EXCEPTION; + } - char *in = strdup(input); - char *line = NULL; - char *saveLinePointer = NULL; + int rc = 0; - bool firstTime = true; - do { - if (firstTime){ - line = strtok_r(in, "\n", &saveLinePointer); - firstTime = false; - }else { - line = strtok_r(NULL, "\n", &saveLinePointer); + if (header && strstr("\n", header)) { + celix_err_push("Header cannot contain newlines. Ignoring header."); + } else if (header) { + rc = fputc('#', file); + if (rc != EOF) { + rc = fputs(header, file); } + if (rc != EOF) { + rc = fputc('\n', file); + } + } - if (line == NULL){ - break; + CELIX_PROPERTIES_ITERATE(properties, iter) { + const char* val = iter.entry.value; + 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); + } + } + 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 CELIX_SUCCESS; +} - parseLine(line, props); - } while(line != NULL); +celix_properties_t* celix_properties_copy(const celix_properties_t* properties) { + celix_properties_t* copy = celix_properties_create(); - free(in); + if (!copy) { + celix_err_push("Failed to create properties copy"); + return NULL; + } - return props; + if (!properties) { + return copy; + } + + CELIX_PROPERTIES_ITERATE(properties, iter) { + celix_status_t status; + if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) { + status = celix_properties_set(copy, iter.key, iter.entry.value); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) { + status = celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) { + status = celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue); + } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) { + status = celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue); + } else /*version*/ { + assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION); + 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; } -void celix_properties_store(celix_properties_t *properties, const char *filename, const char *header) { - FILE *file = fopen (filename, "w+" ); - char *str; +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; +} - 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); - } +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 defaultValue; +} - fputc('=', file); +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; +} - 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); - } +celix_status_t celix_properties_set(celix_properties_t* properties, const char* key, const char* value) { + celix_properties_entry_t prototype = {0}; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING; + prototype.typed.strValue = value; + return celix_properties_createAndSetEntry(properties, key, &prototype); +} - fputc('\n', file); +celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) { + 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."); + free(key); + free(value); + return CELIX_ENOMEM; + } - } - hashMapIterator_destroy(iterator); + bool alreadyExist = celix_stringHashMap_hasKey(properties->map, key); + celix_status_t status = celix_stringHashMap_put(properties->map, key, entry); + 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; + } + 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) { + 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->typed.strValue); } - fclose(file); - } else { - perror("File is null"); } + return CELIX_SUCCESS; // silently ignore NULL entry } -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); - } +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; } - return copy; } -const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue) { - const char* value = NULL; +void celix_properties_unset(celix_properties_t* properties, const char* key) { if (properties != NULL) { - value = hashMap_get((hash_map_t*)properties, (void*)key); + celix_stringHashMap_remove(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) { - 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); +long celix_properties_getAsLong(const celix_properties_t* props, const char* key, long 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) { + return celix_utils_convertStringToLong(entry->value, defaultValue, NULL); } + return defaultValue; } -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); +celix_status_t celix_properties_setLong(celix_properties_t* props, const char* key, long value) { + celix_properties_entry_t prototype = {0}; + 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) { + 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) { + return celix_utils_convertStringToDouble(entry->value, defaultValue, NULL); } + return defaultValue; } -void celix_properties_unset(celix_properties_t *properties, const char *key) { - char* oldValue = hashMap_removeFreeKey(properties, key); - free(oldValue); +celix_status_t celix_properties_setDouble(celix_properties_t* props, const char* key, double val) { + celix_properties_entry_t prototype = {0}; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE; + prototype.typed.doubleValue = val; + return celix_properties_createAndSetEntry(props, key, &prototype); } -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; - } +bool celix_properties_getAsBool(const celix_properties_t* props, const char* key, bool 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) { + return celix_utils_convertStringToBool(entry->value, defaultValue, NULL); } - return result; + return defaultValue; } -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_status_t celix_properties_setBool(celix_properties_t* props, const char* key, bool val) { + celix_properties_entry_t prototype = {0}; + 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, + 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; } + return defaultValue; } -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; +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 result; + return defaultValue == NULL ? NULL : celix_version_copy(defaultValue); } -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_status_t +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 = {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 = {0}; + prototype.valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION; + prototype.typed.versionValue = version; + return celix_properties_createAndSetEntry(props, key, &prototype); +} + +size_t celix_properties_size(const celix_properties_t* properties) { + return celix_stringHashMap_size(properties->map); } -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 = celix_utils_trimInPlace(buf); - if (strncasecmp("true", trimmed, strlen("true")) == 0) { - result = true; - } else if (strncasecmp("false", trimmed, strlen("false")) == 0) { - result = false; +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; + } + 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 result; + return true; } -void celix_properties_setBool(celix_properties_t *props, const char *key, bool val) { - celix_properties_set(props, key, val ? "true" : "false"); -} +typedef struct { + celix_string_hash_map_iterator_t mapIter; + const celix_properties_t* props; +} celix_properties_iterator_internal_t; + +celix_properties_iterator_t celix_properties_begin(const celix_properties_t* properties) { + celix_properties_iterator_t iter; + celix_properties_iterator_internal_t internalIter; + + CELIX_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(iter._data)); -int celix_properties_size(const celix_properties_t *properties) { - return hashMap_size((hash_map_t*)properties); + internalIter.mapIter = celix_stringHashMap_begin(properties->map); + internalIter.props = properties; + + 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)); + } + + memset(&iter._data, 0, sizeof(iter._data)); + memcpy(iter._data, &internalIter, sizeof(internalIter)); + return iter; } -celix_properties_iterator_t celix_propertiesIterator_construct(const celix_properties_t *properties) { +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; - 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; + memset(&iter, 0, sizeof(iter)); + memcpy(iter._data, &internalIter, sizeof(internalIter)); 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; +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)) { + 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)); + } +} + +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); } -celix_properties_t* celix_propertiesIterator_properties(celix_properties_iterator_t *iter) { - return (celix_properties_t*)iter->_data1; +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->_data, sizeof(internalIterA)); + celix_properties_iterator_internal_t internalIterB; + memcpy(&internalIterB, b->_data, sizeof(internalIterB)); + return celix_stringHashMapIterator_equals(&internalIterA.mapIter, &internalIterB.mapIter); +} + +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 / (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); + return stats; } diff --git a/libs/utils/src/utils.c b/libs/utils/src/utils.c index f80b3bd1e..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) { @@ -61,7 +59,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; } } @@ -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])) { @@ -116,7 +114,7 @@ char * string_ndup(const char *s, size_t n) { return ret; } -static char* celix_utilsTrimInternal(char *string) { +static char* celix_utilsTrimInternal(char* string) { if (string == NULL) { return NULL; } @@ -152,6 +150,7 @@ static char* celix_utilsTrimInternal(char *string) { char* celix_utils_trim(const char* string) { return celix_utilsTrimInternal(celix_utils_strdup(string)); } + char* celix_utils_trimInPlace(char* string) { return celix_utilsTrimInternal(string); } @@ -164,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; @@ -314,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); @@ -362,4 +364,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 a8385fd1e..07652a007 100644 --- a/libs/utils/src/version.c +++ b/libs/utils/src/version.c @@ -26,9 +26,12 @@ #include "version.h" #include "celix_errno.h" #include "version_private.h" +#include "celix_err.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); + *version = celix_version_create(major, minor, micro, qualifier); return *version == NULL ? CELIX_ILLEGAL_ARGUMENT : CELIX_SUCCESS; } @@ -95,15 +98,18 @@ 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_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 = ""; + qualifier = CELIX_VERSION_EMPTY_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; @@ -117,34 +123,46 @@ celix_version_t* celix_version_createVersion(int major, int minor, int micro, co 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 = celix_utils_strdup(qualifier); - if (version->qualifier == NULL) { - free(version); + 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; } 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); } } celix_version_t* celix_version_copy(const celix_version_t* version) { - return celix_version_createVersion(version->major, version->minor, version->micro, version->qualifier); + if (version == NULL) { + return celix_version_createEmptyVersion(); + } + return celix_version_create(version->major, version->minor, version->micro, version->qualifier); } @@ -216,7 +234,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) { @@ -228,7 +246,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) { @@ -281,14 +299,28 @@ 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; } +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) { @@ -318,4 +350,4 @@ int celix_version_compareToMajorMinor(const celix_version_t* version, int majorV result = version->minor - minorVersionPart; } return result; -} \ No newline at end of file +}