PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1384609156


##########
libs/utils/src/properties.c:
##########
@@ -191,324 +476,423 @@ static void parseLine(const char* line, 
celix_properties_t *props) {
     }
 
     if (!isComment) {
-        //printf("putting 'key'/'value' '%s'/'%s' in properties\n", 
utils_stringTrim(key), utils_stringTrim(value));
+        // printf("putting 'key'/'value' '%s'/'%s' in properties\n", 
utils_stringTrim(key), utils_stringTrim(value));
         celix_properties_set(props, celix_utils_trimInPlace(key), 
celix_utils_trimInPlace(value));
     }
-    if(key) {
+    if (key) {
         free(key);
-    }
-    if(value) {
         free(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;
+    }
+    size_t fileSize = ftell(file);
+    rc = fseek(file, 0, SEEK_SET);
+    if (rc != 0) {
+        celix_err_pushf("Cannot seek to start of file. Got error %i", errno);
+        return NULL;
+    }
 
+    char* fileBuffer = malloc(fileSize + 1);
+    if (fileBuffer == NULL) {
+        celix_err_pushf("Cannot allocate memory for file buffer. Got error 
%i", errno);
+        return NULL;
+    }
 
-celix_properties_t* celix_properties_create(void) {
-    return hashMap_create(utils_stringHash, utils_stringHash, 
utils_stringEquals, utils_stringEquals);
-}
+    size_t rs = fread(fileBuffer, sizeof(char), fileSize, file);
+    if (rs < fileSize) {
+        fprintf(stderr, "fread read only %zu bytes out of %zu\n", rs, 
fileSize);
+    }
+    fileBuffer[fileSize] = '\0'; // ensure a '\0' at the end of the fileBuffer
 
-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);
+    char* savePtr = NULL;
+    char* line = strtok_r(fileBuffer, "\n", &savePtr);
+    while (line != NULL) {
+        celix_properties_parseLine(line, props);
+        line = strtok_r(NULL, "\n", &savePtr);
     }
+    free(fileBuffer);
+
+    return celix_steal_ptr(props);
 }
 
-celix_properties_t* celix_properties_load(const char *filename) {
-    FILE *file = fopen(filename, "r");
-    if (file == NULL) {
+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;
     }
-    celix_properties_t *props = celix_properties_loadWithStream(file);
-    fclose(file);
-    return props;
-}
-
-celix_properties_t* celix_properties_loadWithStream(FILE *file) {
-    celix_properties_t *props = NULL;
 
-    if (file != NULL ) {
-        char *saveptr;
-        char *filebuffer = NULL;
-        char *line = NULL;
-        ssize_t file_size = 0;
-
-        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_properties_parseLine(line, props);
+        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;
+    for (int i = 0; i < strlen(str); i += 1) {
+        if (str[i] == '#' || str[i] == '!' || str[i] == '=' || str[i] == ':') {
+            rc = fputc('\\', file);
+            if (rc == EOF) {
+                break;
             }
         }
+        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 != 0) {

Review Comment:
   `fputc`/`fputs` return EOF on error



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {

Review Comment:
   How could `long` produces string longer than 32 bytes? If not, this else 
should be removed or replaced by an NEVER_REACH assertion. 



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = *doubleValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%f", entry->typed.doubleValue);
+            entry->value = val;
+        }
+    } else if (boolValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = *boolValue;
+        entry->value = entry->typed.boolValue ? 
CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*versionValue*/ {
+        assert(versionValue != NULL);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = versionValue;
+        bool written = celix_version_fillString(versionValue, 
convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(versionValue);
+        }
+    }
+    if (entry->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_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) {
+        entry = 
&properties->entriesBuffer[properties->currentEntriesBufferIndex++];
+    } else {
+        entry = malloc(sizeof(*entry));
+    }
+    return entry;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization entries 
buffer and take ownership of the
+ * provided key and value strings.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntryWithNoCopy(celix_properties_t* properties,
+                                                                        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;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntry(celix_properties_t* properties,
+                                                              const char** 
strValue,
+                                                              const long* 
longValue,
+                                                              const double* 
doubleValue,
+                                                              const bool* 
boolValue,
+                                                              celix_version_t* 
versionValue) {
+    celix_properties_entry_t* entry = celix_properties_allocEntry(properties);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    celix_status_t status =
+        celix_properties_fillEntry(properties, entry, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (status != CELIX_SUCCESS) {
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+            // entry is part of the properties entries buffer -> nop.
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, 
celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization 
buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* 
properties,
+                                                         const char* key,
+                                                         const char** strValue,
+                                                         const long* longValue,
+                                                         const double* 
doubleValue,
+                                                         const bool* boolValue,
+                                                         celix_version_t* 
versionValue) {

Review Comment:
   I suggest an alternative signature: `static celix_status_t 
celix_properties_createAndSetEntry(celix_properties_t* properties, const 
celix_properties_entry_t* prototype)`, where `value` in `prototype` is `NULL`. 
   
   This is much easier to extend, and it is more intuitive to use (bool*/long* 
is confusing at the first sight).



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = *doubleValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%f", entry->typed.doubleValue);
+            entry->value = val;
+        }
+    } else if (boolValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = *boolValue;
+        entry->value = entry->typed.boolValue ? 
CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*versionValue*/ {
+        assert(versionValue != NULL);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = versionValue;
+        bool written = celix_version_fillString(versionValue, 
convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(versionValue);
+        }
+    }
+    if (entry->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_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) {
+        entry = 
&properties->entriesBuffer[properties->currentEntriesBufferIndex++];
+    } else {
+        entry = malloc(sizeof(*entry));
+    }
+    return entry;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization entries 
buffer and take ownership of the
+ * provided key and value strings.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntryWithNoCopy(celix_properties_t* properties,
+                                                                        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;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntry(celix_properties_t* properties,
+                                                              const char** 
strValue,
+                                                              const long* 
longValue,
+                                                              const double* 
doubleValue,
+                                                              const bool* 
boolValue,
+                                                              celix_version_t* 
versionValue) {
+    celix_properties_entry_t* entry = celix_properties_allocEntry(properties);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    celix_status_t status =
+        celix_properties_fillEntry(properties, entry, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (status != CELIX_SUCCESS) {
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+            // entry is part of the properties entries buffer -> nop.
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, 
celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization 
buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* 
properties,
+                                                         const char* key,
+                                                         const char** strValue,
+                                                         const long* longValue,
+                                                         const double* 
doubleValue,
+                                                         const bool* boolValue,
+                                                         celix_version_t* 
versionValue) {
+    if (!properties) {
+        return CELIX_SUCCESS; // silently ignore
+    }
+    if (!key) {
+        celix_err_push("Cannot set property with NULL key");
+        return CELIX_SUCCESS; // print error and ignore
+    }
+
+    celix_properties_entry_t* entry =
+        celix_properties_createEntry(properties, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (!entry) {
+        return CELIX_ENOMEM;
+    }
+
+    const char* mapKey = key;
+    if (!celix_stringHashMap_hasKey(properties->map, key)) {
+        // new entry, needs new allocated key;
+        mapKey = celix_properties_createString(properties, key);
+        if (!mapKey) {
+            celix_properties_destroyEntry(properties, entry);
+            return CELIX_ENOMEM;
+        }
+    }
+
+    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 = (unsigned 
int)ceil(CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE / 0.75);

Review Comment:
   As remarked previously, 16 slots is enough for 80-160 entries for separate 
chaining hash table.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = *doubleValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%f", entry->typed.doubleValue);
+            entry->value = val;
+        }
+    } else if (boolValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = *boolValue;
+        entry->value = entry->typed.boolValue ? 
CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*versionValue*/ {
+        assert(versionValue != NULL);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = versionValue;
+        bool written = celix_version_fillString(versionValue, 
convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(versionValue);
+        }
+    }
+    if (entry->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_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) {
+        entry = 
&properties->entriesBuffer[properties->currentEntriesBufferIndex++];
+    } else {
+        entry = malloc(sizeof(*entry));
+    }
+    return entry;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization entries 
buffer and take ownership of the
+ * provided key and value strings.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntryWithNoCopy(celix_properties_t* properties,
+                                                                        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;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntry(celix_properties_t* properties,
+                                                              const char** 
strValue,
+                                                              const long* 
longValue,
+                                                              const double* 
doubleValue,
+                                                              const bool* 
boolValue,
+                                                              celix_version_t* 
versionValue) {
+    celix_properties_entry_t* entry = celix_properties_allocEntry(properties);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    celix_status_t status =
+        celix_properties_fillEntry(properties, entry, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (status != CELIX_SUCCESS) {
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+            // entry is part of the properties entries buffer -> nop.
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, 
celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization 
buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* 
properties,
+                                                         const char* key,
+                                                         const char** strValue,
+                                                         const long* longValue,
+                                                         const double* 
doubleValue,
+                                                         const bool* boolValue,
+                                                         celix_version_t* 
versionValue) {

Review Comment:
   This remark also applies to `celix_properties_fillEntry` and 
`celix_properties_createEntry`.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = *doubleValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%f", entry->typed.doubleValue);
+            entry->value = val;
+        }
+    } else if (boolValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = *boolValue;
+        entry->value = entry->typed.boolValue ? 
CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*versionValue*/ {
+        assert(versionValue != NULL);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = versionValue;
+        bool written = celix_version_fillString(versionValue, 
convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(versionValue);
+        }
+    }
+    if (entry->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_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) {
+        entry = 
&properties->entriesBuffer[properties->currentEntriesBufferIndex++];
+    } else {
+        entry = malloc(sizeof(*entry));
+    }
+    return entry;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization entries 
buffer and take ownership of the
+ * provided key and value strings.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntryWithNoCopy(celix_properties_t* properties,
+                                                                        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;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntry(celix_properties_t* properties,
+                                                              const char** 
strValue,
+                                                              const long* 
longValue,
+                                                              const double* 
doubleValue,
+                                                              const bool* 
boolValue,
+                                                              celix_version_t* 
versionValue) {
+    celix_properties_entry_t* entry = celix_properties_allocEntry(properties);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    celix_status_t status =
+        celix_properties_fillEntry(properties, entry, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (status != CELIX_SUCCESS) {
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+            // entry is part of the properties entries buffer -> nop.
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, 
celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization 
buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_status_t celix_properties_createAndSetEntry(celix_properties_t* 
properties,
+                                                         const char* key,
+                                                         const char** strValue,
+                                                         const long* longValue,
+                                                         const double* 
doubleValue,
+                                                         const bool* boolValue,
+                                                         celix_version_t* 
versionValue) {
+    if (!properties) {
+        return CELIX_SUCCESS; // silently ignore
+    }
+    if (!key) {
+        celix_err_push("Cannot set property with NULL key");
+        return CELIX_SUCCESS; // print error and ignore

Review Comment:
   If the caller has no way to know this, error message will accumulate in the 
ERR.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = *doubleValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%f", entry->typed.doubleValue);
+            entry->value = val;
+        }
+    } else if (boolValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = *boolValue;
+        entry->value = entry->typed.boolValue ? 
CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*versionValue*/ {
+        assert(versionValue != NULL);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = versionValue;
+        bool written = celix_version_fillString(versionValue, 
convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(versionValue);
+        }
+    }
+    if (entry->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_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE) {
+        entry = 
&properties->entriesBuffer[properties->currentEntriesBufferIndex++];
+    } else {
+        entry = malloc(sizeof(*entry));
+    }
+    return entry;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization entries 
buffer and take ownership of the
+ * provided key and value strings.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntryWithNoCopy(celix_properties_t* properties,
+                                                                        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;
+}
+
+/**
+ * Create entry and optionally use the short properties optimization buffers.
+ * Only 1 of the types values (strValue, LongValue, etc) should be provided.
+ */
+static celix_properties_entry_t* 
celix_properties_createEntry(celix_properties_t* properties,
+                                                              const char** 
strValue,
+                                                              const long* 
longValue,
+                                                              const double* 
doubleValue,
+                                                              const bool* 
boolValue,
+                                                              celix_version_t* 
versionValue) {
+    celix_properties_entry_t* entry = celix_properties_allocEntry(properties);
+    if (entry == NULL) {
+        return NULL;
+    }
+
+    celix_status_t status =
+        celix_properties_fillEntry(properties, entry, strValue, longValue, 
doubleValue, boolValue, versionValue);
+    if (status != CELIX_SUCCESS) {
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE)) {
+            // entry is part of the properties entries buffer -> nop.

Review Comment:
   Rather than nop, `currentEntriesBufferIndex` should be reverted.



##########
libs/utils/src/properties.c:
##########
@@ -17,55 +17,87 @@
  * under the License.
  */
 
+#include "properties.h"
+#include "celix_properties.h"
+#include "celix_properties_private.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <math.h>
+#include <stdbool.h>
 #include <stdio.h>
-#include <string.h>
 #include <stdlib.h>
-#include <ctype.h>
-#include <stdbool.h>
+#include <string.h>
 
-#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 <errno.h>
-#include "hash_map.h"
+#include "celix_stdlib_cleanup.h"
+#include "celix_convert_utils.h"
 
-#define MALLOC_BLOCK_SIZE        5
+#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE 512
+#define CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE 16
 
-static void parseLine(const char* line, celix_properties_t *props);
+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 = "";
 
-properties_pt properties_create(void) {
-    return celix_properties_create();
-}
+struct celix_properties {
+    celix_string_hash_map_t* map;
 
-void properties_destroy(properties_pt properties) {
-    celix_properties_destroy(properties);
-}
+    /**
+     * String buffer used to store the first key/value entries,
+     * so that in many cases - for usage in service properties - additional 
memory allocations are not needed.
+     *
+     * @note based on some small testing most services properties seem to max 
out at 11 entries.
+     * So 16 (next factor 2 based value) seems like a good fit.
+     */
+    char stringBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE];
 
-properties_pt properties_load(const char* filename) {
-    return celix_properties_load(filename);
-}
+    /**
+     * The current string buffer index.
+     */
+    int currentStringBufferIndex;
 
-properties_pt properties_loadWithStream(FILE *file) {
-    return celix_properties_loadWithStream(file);
-}
+    /**
+     * Entries buffer used to store the first entries, so that in many cases 
additional memory allocation
+     * can be prevented.
+     *
+     * @note based on some small testing most services properties seem to max 
around 300 bytes.
+     * So 512 (next factor 2 based value) seems like a good fit.
+     */
+    celix_properties_entry_t 
entriesBuffer[CELIX_SHORT_PROPERTIES_OPTIMIZATION_ENTRIES_SIZE];
 
-properties_pt properties_loadFromString(const char *input){
-    return celix_properties_loadFromString(input);
-}
+    /**
+     * The current string buffer index.
+     */
+    int currentEntriesBufferIndex;
+};
+
+#define MALLOC_BLOCK_SIZE 5
+
+static void 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;

Review Comment:
   ```suggestion
       return copy == NULL ? CELIX_ENOMEM : CELIX_SUCCESS;
   ```
   `CELIX_ENOMEM` may be more appropriate.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop

Review Comment:
   Rather than nop, `currentStringBufferIndex` should be reverted.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {
+            char* val = NULL;
+            asprintf(&val, "%li", entry->typed.longValue);
+            entry->value = val;
+        }
+    } else if (doubleValue != NULL) {

Review Comment:
   For double, it can take up to 318 bytes (indeed surprisingly long) for "%s" 
`snprintf`.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup 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_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE - 
properties->currentStringBufferIndex;
+    char* result;
+    if (len < left) {
+        
memcpy(&properties->stringBuffer[properties->currentStringBufferIndex], str, 
len);
+        result = 
&properties->stringBuffer[properties->currentStringBufferIndex];
+        properties->currentStringBufferIndex += (int)len;
+    } else {
+        result = celix_utils_strdup(str);
+    }
+    return result;
+}
+/**
+ * Free string, but first check if it a static const char* const string or 
part of the short properties
+ * optimization.
+ */
+static void celix_properties_freeString(celix_properties_t* properties, char* 
str) {
+    if (str == CELIX_PROPERTIES_BOOL_TRUE_STRVAL || str == 
CELIX_PROPERTIES_BOOL_FALSE_STRVAL ||
+        str == CELIX_PROPERTIES_EMPTY_STRVAL) {
+        // str is static const char* const -> nop
+    } else if (str >= properties->stringBuffer &&
+               str < (properties->stringBuffer + 
CELIX_SHORT_PROPERTIES_OPTIMIZATION_STRING_BUFFER_SIZE)) {
+        // str is part of the properties string buffer -> nop
+    } else {
+        free(str);
+    }
+}
+
+/**
+ * Fill entry and optional use the short properties optimization string buffer.
+ */
+static celix_status_t celix_properties_fillEntry(celix_properties_t* 
properties,
+                                                 celix_properties_entry_t* 
entry,
+                                                 const char** strValue,
+                                                 const long* longValue,
+                                                 const double* doubleValue,
+                                                 const bool* boolValue,
+                                                 celix_version_t* 
versionValue) {
+    char convertedValueBuffer[32];
+    if (strValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, *strValue);
+        entry->typed.strValue = entry->value;
+    } else if (longValue != NULL) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = *longValue;
+        int written = snprintf(convertedValueBuffer, 
sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written >= sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, 
convertedValueBuffer);
+        } else {

Review Comment:
   For 64-bit long, 20 bytes should be enough.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +132,275 @@ 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 strup or storing 
the string the short properties

Review Comment:
   ```suggestion
    * Create a new string from the provided str by either using strdup or 
storing the string the short properties
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@celix.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to