You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@celix.apache.org by GitBox <gi...@apache.org> on 2023/01/12 10:50:11 UTC

[GitHub] [celix] pnoltes opened a new pull request, #470: [RFC] type support for properties

pnoltes opened a new pull request, #470:
URL: https://github.com/apache/celix/pull/470

   This PR add some type support to `celix_properties_t`. Type support for properties are added so that #432 can be solved in type-aware manner. 
   
   Looking at the LDAP spec, the filter attribute type should  lead to the correct order comparison. 
   And because in Celix a "service.version" properties is used to identify the runtime version of a service, it should be possible to add a "service.version" property value as `celix_version_t` type so that `celix_version_compareTo` can be used to compare "celix.version" attributes.
   
   For now I want to keep this PR draft, for 2 reasons:
    - In PR celix properties are no longer a typedef of hash_map, and this is a backwards incompatible update. IMO this PR should only be merged if the next Celix release is going to be a 3.0.0 release
    - Concerning #432, I think it is better to first solve this differently. Akin what @tira-misu purposes in the issue. Although I think adding some type support for properties is eventually a better solution (especially considering performance), for now a solution that does not require a major version update is preferred. 
   
   But comments are welcome :)
   
   Overview changes:
    - Add missing doxygen for celix_properties.h
    - Reimplement celix_properties_t using celix_string_hash_map_t instead of hashmap
    - Add support for long, double, bool and celix_version_t types for property values
    - Add a "short properties" optimization, so that most service properties only need a single malloc.
    - Move and update version cpputest to gtest
    - Move and update properties cpputest to gtest
    - Add C++ Version wrapper for the celix_version_t
    - Replace usage of hashMapIterator on properties and CELIX_PROPERTIES_FOR_EACH to CELIX_PROPERTIES_ITERATE


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390458391


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   Here are the hash function benchmark results (on my machine, using a release build type and cpupower on performance):
   
   hash1 is the long hash function used in celix_long_hash_map at the start of this PR
   hash2 is the long hash function used in the deprecated hashmap 
   hash3 is a altered long hash function which uses a prime.
   
   I also had a short look into [murmur3](https://github.com/logrhythm/MurMurHash/blob/master/src/MurMurHash.cpp) and [FNV](https://github.com/fnvhash/libfnv/tree/master) hash, but for the moment I think this requires too much work (technical and license wise)
   
   # Hash1, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.432 ns        0.432 ns   1000000000 items_per_second=2.31246G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.433 ns        0.433 ns   1000000000 items_per_second=2.31041G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.433 ns        0.433 ns   1000000000 items_per_second=2.31151G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.434 ns        0.434 ns   1000000000 items_per_second=2.30551G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.433 ns        0.433 ns   1000000000 items_per_second=2.31037G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.433 ns        0.432 ns   1000000000 items_per_second=2.30713G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.38 ns         2.38 ns    293482551 averageNrOfEntriesPerBucket=0.625 items_per_second=420.077M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.64 ns         2.63 ns    268351618 averageNrOfEntriesPerBucket=3.125 items_per_second=379.474M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.91621
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.16 ns         2.16 ns    323130939 averageNrOfEntriesPerBucket=3.90625 items_per_second=462.94M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=1.80467
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.90 ns         3.89 ns    183397882 averageNrOfEntriesPerBucket=4.88281 items_per_second=256.477M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.20062
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.38 ns         2.38 ns    294279918 averageNrOfEntriesPerBucket=3.75601 items_per_second=420.113M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.93968
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.62 ns         2.62 ns    270684694 averageNrOfEntriesPerBucket=4.98246 items_per_second=380.976M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23419
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.67 ns         3.67 ns    190401851 items_per_second=272.19M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.69 ns         3.68 ns    190481474 items_per_second=271.21M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.69 ns         3.68 ns    189597715 items_per_second=271.084M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.69 ns         3.68 ns    189618122 items_per_second=271.035M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.69 ns         3.68 ns    189681903 items_per_second=271.169M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.67 ns         3.67 ns    190523011 items_per_second=272.179M/s
   ```
   
   
   #Hash 2, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.428 ns        0.428 ns   1000000000 items_per_second=2.33786G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.427 ns        0.427 ns   1000000000 items_per_second=2.34001G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.428 ns        0.428 ns   1000000000 items_per_second=2.33579G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.427 ns        0.427 ns   1000000000 items_per_second=2.3403G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.427 ns        0.427 ns   1000000000 items_per_second=2.34037G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.428 ns        0.428 ns   1000000000 items_per_second=2.33835G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.56 ns         2.56 ns    272918800 averageNrOfEntriesPerBucket=0.625 items_per_second=390.34M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.695971
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.76 ns         3.76 ns    186188393 averageNrOfEntriesPerBucket=3.125 items_per_second=266.203M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.93245
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.78 ns         2.78 ns    251731622 averageNrOfEntriesPerBucket=3.90625 items_per_second=359.86M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.00366
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.99 ns         2.99 ns    233585592 averageNrOfEntriesPerBucket=4.88281 items_per_second=333.894M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.14352
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             3.00 ns         3.00 ns    233887362 averageNrOfEntriesPerBucket=3.75601 items_per_second=333.838M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.94674
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.99 ns         2.99 ns    234075293 averageNrOfEntriesPerBucket=4.98246 items_per_second=334.062M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.22839
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.64 ns         3.64 ns    192084107 items_per_second=274.697M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.64 ns         3.64 ns    192187264 items_per_second=274.762M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.64 ns         3.64 ns    192202353 items_per_second=274.977M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.63 ns         3.63 ns    192491569 items_per_second=275.124M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.63 ns         3.63 ns    192638166 items_per_second=275.242M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.63 ns         3.63 ns    193028763 items_per_second=275.699M/s
   ```
   
   # Hash3, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.414 ns        0.414 ns   1000000000 items_per_second=2.4128G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.414 ns        0.414 ns   1000000000 items_per_second=2.41719G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.414 ns        0.414 ns   1000000000 items_per_second=2.41582G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.414 ns        0.414 ns   1000000000 items_per_second=2.41681G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.414 ns        0.414 ns   1000000000 items_per_second=2.41521G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.414 ns        0.414 ns   1000000000 items_per_second=2.41732G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.26 ns         2.26 ns    307909253 averageNrOfEntriesPerBucket=0.625 items_per_second=441.585M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.39 ns         3.39 ns    211113789 averageNrOfEntriesPerBucket=3.125 items_per_second=295.319M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.86665
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.48 ns         2.48 ns    281288298 averageNrOfEntriesPerBucket=3.90625 items_per_second=403.557M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.16123
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.31 ns         3.31 ns    211375732 averageNrOfEntriesPerBucket=4.88281 items_per_second=302.161M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.2035
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.48 ns         2.48 ns    282405576 averageNrOfEntriesPerBucket=3.75601 items_per_second=403.207M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.92598
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.90 ns         2.90 ns    241719478 averageNrOfEntriesPerBucket=4.98246 items_per_second=344.381M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23011
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.54 ns         3.54 ns    197602959 items_per_second=282.865M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.54 ns         3.53 ns    197704646 items_per_second=282.885M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    197684458 items_per_second=282.891M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.54 ns         3.54 ns    197731536 items_per_second=282.88M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.54 ns         3.54 ns    197896830 items_per_second=282.817M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.54 ns         3.54 ns    198110019 items_per_second=282.856M/s
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378319542


##########
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;

Review Comment:
   > When adding a property with a string value if NULL, a empty string ("") will be used instead using the celix_properties_createString function
   
   This is nice and improves general user experience.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382496338


##########
libs/utils/include/celix_version.h:
##########
@@ -20,6 +20,11 @@
 #ifndef CELIX_CELIX_VERSION_H
 #define CELIX_CELIX_VERSION_H
 
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include "celix_utils_export.h"

Review Comment:
   The above includes duplicate the following includes.



##########
libs/utils/src/version.c:
##########
@@ -95,15 +97,20 @@ celix_status_t version_isCompatible(version_pt user, version_pt provider, bool*
     return CELIX_SUCCESS;
 }
 
-celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier) {
+celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char * qualifier) {
+    return celix_version_create(major, minor, micro, qualifier);
+}
+

Review Comment:
   ```suggestion
   ```
   Not used any more.



##########
libs/utils/include/celix_version.h:
##########
@@ -77,27 +97,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 NULL if no qualifier is present.

Review Comment:
   Empty string if no qualifier is present?



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -73,10 +61,11 @@ typedef struct celix_hash_map {
     bool (*equalsKeyFunction)(const celix_hash_map_key_t* key1, const celix_hash_map_key_t* key2);
     void (*simpleRemovedCallback)(void* value);
     void* removedCallbackData;
-    void (*removedStringKeyCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue);
-    void (*removedLongKeyCallback)(void* data, long removedKey, celix_hash_map_value_t removedValue);
+    void (*removedStringEntryCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue);

Review Comment:
   Is `emptyValue` unused? If so, it should be removed.



##########
libs/utils/src/version.c:
##########
@@ -289,6 +301,15 @@ char* celix_version_toString(const celix_version_t* version) {
     return string;
 }
 
+bool celix_version_fillString(const celix_version_t* version, char *str, size_t strLen) {

Review Comment:
   The return value of `asprintf` in `celix_version_toString` is not checked.



##########
libs/utils/include/celix_version.h:
##########
@@ -30,37 +35,52 @@ extern "C" {
 #include "celix_utils_export.h"
 
 /**
- * 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.
  *
- * @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
- *        <code>null</code> 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
+ *        <code>NULL</code> is specified, then the qualifier will be set to
  *        the empty string.
  * @return The created version or NULL if the input was incorrect
  */
-CELIX_UTILS_EXPORT celix_version_t* celix_version_createVersion(int major, int minor, int micro, const char* qualifier);
+CELIX_UTILS_EXPORT celix_version_t* celix_version_create(int major, int minor, int micro, const char* qualifier);
+
+/**
+ * @deprecated Use celix_version_create instead.
+ */
+CELIX_UTILS_DEPRECATED_EXPORT celix_version_t* celixversion_createVersion(int major, int minor, int micro, const char* qualifier);

Review Comment:
   ```suggestion
   ```
   
   Not used anymore.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398127100


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,265 @@ 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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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 = (unsigned int)ceil(CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE / 0.75);

Review Comment:
   ```suggestion
           opts.initialCapacity = CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE;
   ```
   A default load factor of 0.75 no longer applies.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1388473469


##########
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 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_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);
+        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 void celix_properties_parseLine(const char* line, celix_properties_t* props) {

Review Comment:
   I agree that this can and should be improved, but I propose to do this in a separate PR: #685. 
   Again to prevent that the scope of this PR becomes much larger. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398119159


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,278 @@ 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];
+    if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = prototype->typed.versionValue;
+        bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(prototype->typed.versionValue);
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = prototype->typed.longValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written >= 0 || written < sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            //LCOV_EXCL_START
+            assert(false); // should not happen, LONG_MAX str is 19 chars, LONG_MIN str is 20 chars
+            //LCOV_EXCL_STOP
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = prototype->typed.doubleValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written <= sizeof(convertedValueBuffer)) {
+            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->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = prototype->typed.boolValue;
+        entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*string value*/ {
+        assert(prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, prototype->typed.strValue);
+        entry->typed.strValue = entry->value;
+    }
+
+    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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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) {

Review Comment:
   We can avoid this if it were done in `celix_properties_setVersion`/`celix_properties_setVersionWithoutCopy`.
   
   The code duplication is minimal.



##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,265 @@ 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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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) {

Review Comment:
   The above remark also applies here.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390439371


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -237,11 +235,12 @@ static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, con
     newEntry->next = entry;
     map->buckets[bucketIndex] = newEntry;
     if (map->size++ >= celix_hashMap_threshold(map)) {
-        celix_hashMap_resize(map, 2 * map->bucketsSize);
+        return celix_hashMap_resize(map, 2 * map->bucketsSize);

Review Comment:
   IMO it should fail, else we have a error which is not reported or logged to celix_err without the users knowing a issue occurred. 
   
   Refactored the hashmap resize so that this is done before creating a new entry. When resize fails - or when creating a new entry fails - users still have ownership of weakly stored keys. I also updated the header dox for the storeKeysWeakly to clarify this. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390439644


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;
     entry = celix_hashMap_nextEntry(map, entry);
-    if (entry != NULL) {
+    if (entry) {
         iter->_internal[1] = entry;
-        iter->index += 1;
         iter->key = entry->key.strKey;
         iter->value = entry->value;
     } else {
-        memset(iter, 0, sizeof(*iter));
-        iter->_internal[0] = (void*)map;
+        iter->_internal[1] = NULL;
+        iter->key = NULL;

Review Comment:
   nice catch, this should be aligned. Updated the next so that the key is set to `""`. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378679080


##########
libs/utils/gtest/CMakeLists.txt:
##########
@@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils)
 configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
 
 if (CELIX_CXX17)
-    add_library(test_utils_cxx17tests STATIC
+    add_library(test_utils_cxx17tests OBJECT

Review Comment:
   ~~I have not figured out why static library does not work.~~
   All unresolved symbols of the executable `test_utils` are satisfied by other libraries, and it does not use any symbols in `test_utils_cxx17tests`. Therefore, `test_utils_cxx17tests` is totally ignored.
   
   Changing `test_utils_cxx17tests` to object library essentially makes it part of `test_utils`.



##########
libs/utils/gtest/CMakeLists.txt:
##########
@@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils)
 configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
 
 if (CELIX_CXX17)
-    add_library(test_utils_cxx17tests STATIC
+    add_library(test_utils_cxx17tests OBJECT

Review Comment:
   ~~I have not figured out why static library does not work.~~
   All unresolved symbols of the executable `test_utils` are satisfied by other libraries, and it does not use any symbols in `test_utils_cxx17tests`. Therefore, `test_utils_cxx17tests` is totally ignored by linker.
   
   Changing `test_utils_cxx17tests` to object library essentially makes it part of `test_utils`.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1777000802

   I'll try to finish reviewing this PR this week.


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382402803


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or

Review Comment:
   `key ` is actually moved into properties. In case of error, it is freed. Thus it should not be touched after this function returns. Warning that "it must not be freed or modified after calling this function" is not enough.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398331655


##########
libs/utils/src/properties.c:
##########
@@ -117,69 +409,62 @@ static void parseLine(const char* line, celix_properties_t *props) {
     output = NULL;
     outputPos = 0;
 
-    //Ignore empty lines
+    // Ignore empty lines
     if (line[0] == '\n' && line[1] == '\0') {
         return;
     }
 
-    char *key = calloc(1, key_len);
-    char *value = calloc(1, value_len);
+    char* key = calloc(1, key_len);
+    char* value = calloc(1, value_len);
     key[0] = '\0';
     value[0] = '\0';
 
     while (line[linePos] != '\0') {
         if (line[linePos] == ' ' || line[linePos] == '\t') {
             if (output == NULL) {
-                //ignore
+                // ignore
                 linePos += 1;
                 continue;
             }
-        }
-        else {
+        } else {
             if (output == NULL) {
                 output = key;
             }
         }
         if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') {
             if (precedingCharIsBackslash) {
-                //escaped special character
+                // escaped special character
                 output[outputPos++] = line[linePos];
                 updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len);

Review Comment:
   It can be delayed until we solve #685.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398119159


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,278 @@ 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];
+    if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = prototype->typed.versionValue;
+        bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(prototype->typed.versionValue);
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = prototype->typed.longValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written >= 0 || written < sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            //LCOV_EXCL_START
+            assert(false); // should not happen, LONG_MAX str is 19 chars, LONG_MIN str is 20 chars
+            //LCOV_EXCL_STOP
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = prototype->typed.doubleValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written <= sizeof(convertedValueBuffer)) {
+            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->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = prototype->typed.boolValue;
+        entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*string value*/ {
+        assert(prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, prototype->typed.strValue);
+        entry->typed.strValue = entry->value;
+    }
+
+    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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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) {

Review Comment:
   We can avoid this if it were done in callers.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1817543557

   @PengZheng  I think I reworked all remarks. 
   
   Could you re-review?


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1819541539

   > 
   > Therefore, it is also reasonable to remove `index` from the interface and implementation. WDYT?
   
   I agree. The index field is too confusion and it is trivial to add a counter/index on your own, so I removed the index field.


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382404373


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or

Review Comment:
   We also need to emphasize that `key` will be freed by `free`. It can not come from arbitrary  memory allocator.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398122940


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,265 @@ 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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;

Review Comment:
   Yes, this fixed the issue. 
   I just noticed we have duplicated code of entry deallocation here and in `celix_properties_destroyEntry`.
   To destroy an entry, we must deinitialise then deallocate it. 
   In the `celix_properties_deallocEntry`, we can check if the entry is the last one. 
   If it is indeed the last one in pool, then we can perform `currentEntriesBufferIndex -= 1`.
   
   This consideration may/may not also apply to https://github.com/apache/celix/pull/470#discussion_r1397781840
   So that we don't introduce much complexity. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398120010


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,265 @@ 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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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) {

Review Comment:
   The above remark also applies here.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes merged PR #470:
URL: https://github.com/apache/celix/pull/470


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382398863


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *

Review Comment:
   We need to document ERR's usage.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389419559


##########
libs/utils/include/celix/Version.h:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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 <memory>
+#include <string>
+
+#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.
+         */
+#if __cplusplus >= 201703L //C++17 or higher
+        Version(int major, int minor, int micro, std::string_view qualifier = {}) :
+            cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))},
+            qualifier{celix_version_getQualifier(cVersion.get())} {}
+#else
+        explicit Version(int major, int minor, int micro, const std::string& qualifier = {}) :
+            cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))},
+            qualifier{celix_version_getQualifier(cVersion.get())} {}
+#endif
+
+        ///@brief Move-constructs a new version from an existing one.
+        Version(Version&&) = default;

Review Comment:
   same as with Properties. cVersion will be nullptr and as far as I know that is ok for a move. 



-- 
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-unsubscribe@celix.apache.org

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


[GitHub] [celix] pnoltes commented on pull request #470: [RFC] type support for properties

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1418985583

   When #476 is merged, update this PR to use the celix utils string convert to functions


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1383248138


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -237,11 +235,12 @@ static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, con
     newEntry->next = entry;
     map->buckets[bucketIndex] = newEntry;
     if (map->size++ >= celix_hashMap_threshold(map)) {
-        celix_hashMap_resize(map, 2 * map->bucketsSize);
+        return celix_hashMap_resize(map, 2 * map->bucketsSize);

Review Comment:
   if `newEntry` is added but `celix_hashMap_resize` failed, should it return SUCCESS or error code? 



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;
     entry = celix_hashMap_nextEntry(map, entry);
-    if (entry != NULL) {
+    if (entry) {
         iter->_internal[1] = entry;
-        iter->index += 1;
         iter->key = entry->key.strKey;
         iter->value = entry->value;
     } else {
-        memset(iter, 0, sizeof(*iter));
-        iter->_internal[0] = (void*)map;
+        iter->_internal[1] = NULL;
+        iter->key = NULL;

Review Comment:
   In `celix_stringHashMap_end`, `key` is set to "".



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   `loadFactor`, which is always less than 1,  does not apply to separate chaining hash table.  It does apply to linear probing and double hashing implementation, both of which have nothing to do with our implementation.
   
   For separate chaining implementation, it is normal for a list to contain about 5 or 10 keys.  Our resize implementation will lead to too many unnecessary resize.
   
   For reference, I quote an exercise in Robert Sedgewick's Algorithms in C++:
   
   >  14.45 Implement a version of Program 14.7 for separate chaining that increases the table size by a factor of 10 each time the average list length is equal to 10.
   
   



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -237,11 +235,12 @@ static void celix_hashMap_addEntry(celix_hash_map_t* map, unsigned int hash, con
     newEntry->next = entry;
     map->buckets[bucketIndex] = newEntry;
     if (map->size++ >= celix_hashMap_threshold(map)) {
-        celix_hashMap_resize(map, 2 * map->bucketsSize);
+        return celix_hashMap_resize(map, 2 * map->bucketsSize);

Review Comment:
   For weakly stored key, it will make huge difference.



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;

Review Comment:
   `iter.index` will be larger than  `map->genericMap.size` even if it has reached the end. Does this break some invariant?



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -562,6 +591,33 @@ void celix_longHashMap_clear(celix_long_hash_map_t* map) {
     celix_hashMap_clear(&map->genericMap);
 }
 
+static bool celix_hashMap_equals(const celix_hash_map_t* map1, const celix_hash_map_t* map2) {
+    if (map1 == NULL && map2 == NULL) {
+        return true;
+    }
+    if (map1 == NULL || map2 == NULL) {
+            return false;
+    }
+    if (map1->size != map2->size) {
+        return false;
+    }
+    for (celix_hash_map_entry_t* entry = celix_hashMap_firstEntry(map1); entry != NULL; entry = celix_hashMap_nextEntry(map1, entry)) {
+        void* value = celix_hashMap_get(map2, entry->key.strKey, entry->key.longKey);
+        if (value == NULL || memcmp(&entry->value, value, sizeof(entry->value)) != 0) {

Review Comment:
   This assumes `sizeof(void*) >= sizeof(double)`, which is not true on ARM32. `celix_hashMap_get` should return `celix_hash_map_entry_t` instead.



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -256,57 +255,46 @@ static bool celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, lo
             if (replacedValueOut != NULL) {
                 *replacedValueOut = entry->value;
             }
+            celix_hashMap_callRemovedCallback(map, entry);
             memcpy(&entry->value, value, sizeof(*value));
-            return true;
+            return CELIX_SUCCESS;
         }
     }
-    celix_hashMap_addEntry(map, hash, &key, value, index);
-    if (replacedValueOut != NULL) {
+    celix_status_t status = celix_hashMap_addEntry(map, hash, &key, value, index);
+    if (status == CELIX_SUCCESS && replacedValueOut != NULL) {
         memset(replacedValueOut, 0, sizeof(*replacedValueOut));
     }
-    return false;
+    return status;
 }
 
-static void* celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v) {
+static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v, void** previousValueOut) {

Review Comment:
   `previousValueOut` is unused. Shall we remove it?



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -94,11 +83,11 @@ static unsigned int celix_longHashMap_hash(const celix_hash_map_key_t* key) {
     return key->longKey ^ (key->longKey >> (sizeof(key->longKey)*8/2));

Review Comment:
   Suppose that `sizeof(long)==8`, when the table size is very small (like 16), only 8 bits of `longKey` (the lower 4 bits of the upper and lower 32bit) will affect the resulting hash value. This is a bad hash functino.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382405959


##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();

Review Comment:
   If any of the copy failed, we'd better cleanup and return null. Dropping property silently is not a good idea.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390185418


##########
libs/utils/include/celix_string_hash_map.h:
##########
@@ -90,14 +94,38 @@ 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 should free the provided key.

Review Comment:
   ```suggestion
        * instead keep the already existing key. If needed, the caller may free the provided key.
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398214258


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,509 @@
  * 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 <stdio.h>
-#include <stdbool.h>
 
 #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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief The current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] filename The name of the file to load properties from.
+ * @return A property set containing the properties from the file.
+ * @retval NULL If an error occurred (e.g. file not found).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filename);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties,
+                                                                  char* key,
+                                                                  char* value);
 
-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 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);
 
-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 Set the value of a property to a long integer.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 Set the value of a property to a boolean.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] val The boolean value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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 int celix_properties_size(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property to a double.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 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);
 
-#define CELIX_PROPERTIES_FOR_EACH(props, key) _CELIX_PROPERTIES_FOR_EACH(CELIX_UNIQUE_ID(iter), props, key)
+/**
+ * @brief Set the value of a property as a Celix version.
+ *
+ * This function will make a copy of the provided celix_version_t object and store it in the property set.
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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);
 
-#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 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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] version The value to set. The function will store a reference to this object in the property set and
+ *                    takes ownership of the provided version.
+ *

Review Comment:
   Added return doc



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1397775794


##########
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:
   nice catch



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389333404


##########
libs/utils/include/celix/Properties.h:
##########
@@ -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;

Review Comment:
   Yes, I think cProps will then be nullptr.. but as for as I know a variable to a moved object should not be used, expect to potentially reassign a new object. or reinitialized the object.
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378679080


##########
libs/utils/gtest/CMakeLists.txt:
##########
@@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils)
 configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
 
 if (CELIX_CXX17)
-    add_library(test_utils_cxx17tests STATIC
+    add_library(test_utils_cxx17tests OBJECT

Review Comment:
   I have not figured out why static library does not work.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378679080


##########
libs/utils/gtest/CMakeLists.txt:
##########
@@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils)
 configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
 
 if (CELIX_CXX17)
-    add_library(test_utils_cxx17tests STATIC
+    add_library(test_utils_cxx17tests OBJECT

Review Comment:
   ~~I have not figured out why static library does not work.~~
   All unresolved symbols of the executable `test_utils` are satisfied by other libraries, and it does not use any symbols in `test_utils_cxx17tests`. Therefore, `test_utils_cxx17tests` is totally ignored by linker. 
   That is one subtlety of using static library.
   
   Changing `test_utils_cxx17tests` to object library essentially makes it part of `test_utils`.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "xuzhenbao (via GitHub)" <gi...@apache.org>.
xuzhenbao commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378321550


##########
bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c:
##########
@@ -425,6 +424,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno
                     break;
                 }
                 TXTRecordDeallocate(&txtRecord);
+                celix_propertiesIterator_next(&propIter);

Review Comment:
   I think `discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord` also should be invoked before `while (!celix_propertiesIterator_isEnd(&propIter))` . otherwise, a same property is set  in `txtRecord`.
   
   We can resolve it in another way: Only let `discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord` do `celix_propertiesIterator_next` whether txtRecord is filled or not.
   ~~~
   static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroconf_announcer_t *announcer, celix_properties_iterator_t *propIter, TXTRecordRef *txtRecord, uint16_t maxTxtLen, bool splitTxtRecord) {
       const char *key;
       const char *val;
       while (!celix_propertiesIterator_isEnd(propIter)) {
           key = propIter->key;
           val = propIter->entry.value;
           if (key) {
               DNSServiceErrorType err = TXTRecordSetValue(txtRecord, key, strlen(val), val);
               if (err != kDNSServiceErr_NoError) {
                   celix_logHelper_error(announcer->logHelper, "Announcer: Failed to set txt value, %d.", err);
                   return false;
               }
               if (splitTxtRecord && TXTRecordGetLength(txtRecord) >= maxTxtLen - UINT8_MAX) {
                   celix_propertiesIterator_next(propIter);
                   break;
               }
           }
           celix_propertiesIterator_next(propIter);
       }
       return true;
   }
   ~~~



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1388340942


##########
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

Review Comment:
   I will make this configurable (as is done with `celix_err_buffer_size`) and set the default to 128. 
   
   I also created a issue to #684 so that users can get some statistics about the service framework and use that to determine (if needed) the ideal properties optimization size for their use cases. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389236742


##########
libs/utils/gtest/src/HashMapTestSuite.cc:
##########
@@ -534,25 +514,159 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) {
     auto iter2 = celix_longHashMap_begin(lMap);
     while (!celix_longHashMapIterator_isEnd(&iter2)) {
         if (iter2.index % 2 == 0) {
-            //note only removing entries where the iter index is even
+            // note only removing entries where the iter index is even
             celix_longHashMapIterator_remove(&iter2);
         } else {
             celix_longHashMapIterator_next(&iter2);
         }
     }
     EXPECT_EQ(3, celix_longHashMap_size(lMap));
     EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2));
-    celix_longHashMapIterator_next(&iter2); //calling next on end iter, does nothing
+    celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing
     EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2));
     celix_longHashMap_destroy(lMap);
 }
 
-TEST_F(HashMapTestSuite, StringHashMapCleanup) {
+TEST_F(HashMapTestSuite, IterateEndTest) {
+    auto* sMap1 = createStringHashMap(0);
+    auto* sMap2 = createStringHashMap(6);
+    auto* lMap1 = createLongHashMap(0);
+    auto* lMap2 = createLongHashMap(6);
+
+    auto sIter1 = celix_stringHashMap_end(sMap1);
+    auto sIter2 = celix_stringHashMap_end(sMap2);
+    auto lIter1 = celix_longHashMap_end(lMap1);
+    auto lIter2 = celix_longHashMap_end(lMap2);
+
+    EXPECT_EQ(sIter1.index, 0);
+    EXPECT_EQ(sIter2.index, 6);
+    EXPECT_EQ(lIter1.index, 0);
+    EXPECT_EQ(lIter2.index, 6);
+    EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter1));
+    EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter2));
+    EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1));
+    EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter2));
+
+    celix_stringHashMap_destroy(sMap1);
+    celix_stringHashMap_destroy(sMap2);
+    celix_longHashMap_destroy(lMap1);
+    celix_longHashMap_destroy(lMap2);
+}
+
+TEST_F(HashMapTestSuite, EqualsTest) {
+    auto* sMap = createStringHashMap(2);
+    auto sIter1 = celix_stringHashMap_begin(sMap);
+    auto sIter2 = celix_stringHashMap_begin(sMap);
+
+    // Test equal iterators
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Test unequal iterators after only 1 modification
+    celix_stringHashMapIterator_next(&sIter1);
+    EXPECT_FALSE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Test equal iterators after both modification
+    celix_stringHashMapIterator_next(&sIter2);
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Same for long hash map
+    auto* lMap = createLongHashMap(1);
+    auto lIter1 = celix_longHashMap_begin(lMap);
+    auto lIter2 = celix_longHashMap_begin(lMap);
+
+    // Test equal iterators
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    // Test unequal iterators after only 1 modification
+    celix_longHashMapIterator_next(&lIter1);
+    EXPECT_FALSE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    // Test equal iterators after both modification
+    celix_longHashMapIterator_next(&lIter2);
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    celix_stringHashMap_destroy(sMap);
+    celix_longHashMap_destroy(lMap);
+}
+
+TEST_F(HashMapTestSuite, EqualsZeroSizeMapTest) {
+    // Because map size is 0, begin iter should equal end iter
+    auto* sMap = createStringHashMap(0);
+    auto sIter1 = celix_stringHashMap_begin(sMap);
+    auto sEnd = celix_stringHashMap_end(sMap);
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sEnd));
+
+    // Because map size is 0, begin iter should equal end iter
+    auto* lMap = createLongHashMap(0);
+    auto lIter1 = celix_longHashMap_begin(lMap);
+    auto lEnd = celix_longHashMap_end(lMap);
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lEnd));
+
+    celix_stringHashMap_destroy(sMap);
+    celix_longHashMap_destroy(lMap);
+}
+
+TEST_F(HashMapTestSuite, StoreKeysWeaklyTest) {
+    celix_string_hash_map_create_options_t opts{};
+    opts.removedCallbackData = (void*)0x1;
+    opts.storeKeysWeakly = true;
+    opts.removedKeyCallback = [](void* data, char* key) {
+        EXPECT_EQ(data, (void*)0x1);
+        free(key);
+    };
+    auto* sMap = celix_stringHashMap_createWithOptions(&opts);
+    EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); // new key -> takes ownership

Review Comment:
   The tests have been updated for the changed return type. 
   But I think the `pts.removedKeyCallback` documentation is still valid and not changed, or am I missing something?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1380068055


##########
libs/utils/include/celix/Properties.h:
##########
@@ -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;

Review Comment:
   After move, the stored pointer in `cProps` is nullptr? That will break class invariant and lead to crash.



##########
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 `CELIX_SUCCESS` is returned, how the caller can know he/she needs to log ERR?



##########
libs/utils/include/celix_string_hash_map.h:
##########
@@ -90,14 +94,37 @@ typedef struct celix_string_hash_map_create_options {
      * only the simpledRemoveCallback will be used.
      *
      * Default is NULL.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] removedKey The key of the value that was removed from the hash map.
+     *                       Note that the removedKey can still be in use if the a value entry for the same key is
+     *                       replaced, so this callback should not free the removedKey.
+     * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the
+     *                         hash map and can be freed.
      */
     void (*removedCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT;
 
+    /**
+     * @brief A removed key callback, which if provided will be called if a key is no longer used in the hash map.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] key The key that is no longer used in the hash map. if `storeKeysWeakly` was configured as true,
+     *                the key can be freed.
+     */
+    void (*removedKeyCallback)(void* data, char* key) CELIX_OPTS_INIT;
+
     /**
      * @brief If set to true, the string hash map will not make of copy of the keys and assumes
      * that the keys are in scope/memory for the complete lifecycle of the string hash map.
      *
-     * Note that this changes the default behaviour of the celix_stringHashMap_put* functions.
+     * When keys are stored weakly it is the caller responsibility to check the return value of
+     * celix_stringHashMap_put* function calls.
+     * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key

Review Comment:
   It no longer returns true/false.



##########
libs/utils/include/celix/Version.h:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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 <memory>
+#include <string>
+
+#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.
+         */
+#if __cplusplus >= 201703L //C++17 or higher
+        Version(int major, int minor, int micro, std::string_view qualifier = {}) :
+            cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.data()))},
+            qualifier{celix_version_getQualifier(cVersion.get())} {}
+#else
+        explicit Version(int major, int minor, int micro, const std::string& qualifier = {}) :
+            cVersion{createVersion(celix_version_create(major, minor, micro, qualifier.empty() ? "" : qualifier.c_str()))},
+            qualifier{celix_version_getQualifier(cVersion.get())} {}
+#endif
+
+        ///@brief Move-constructs a new version from an existing one.
+        Version(Version&&) = default;

Review Comment:
   What's the status of `cVersion` after move? Will this break class invariant (and lead to crash)?



##########
libs/utils/include/celix_string_hash_map.h:
##########
@@ -90,14 +94,37 @@ typedef struct celix_string_hash_map_create_options {
      * only the simpledRemoveCallback will be used.
      *
      * Default is NULL.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] removedKey The key of the value that was removed from the hash map.
+     *                       Note that the removedKey can still be in use if the a value entry for the same key is
+     *                       replaced, so this callback should not free the removedKey.
+     * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the
+     *                         hash map and can be freed.
      */
     void (*removedCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT;
 
+    /**
+     * @brief A removed key callback, which if provided will be called if a key is no longer used in the hash map.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] key The key that is no longer used in the hash map. if `storeKeysWeakly` was configured as true,
+     *                the key can be freed.
+     */
+    void (*removedKeyCallback)(void* data, char* key) CELIX_OPTS_INIT;
+
     /**
      * @brief If set to true, the string hash map will not make of copy of the keys and assumes
      * that the keys are in scope/memory for the complete lifecycle of the string hash map.
      *
-     * Note that this changes the default behaviour of the celix_stringHashMap_put* functions.
+     * When keys are stored weakly it is the caller responsibility to check the return value of
+     * celix_stringHashMap_put* function calls.
+     * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key
+     * should never be freed or freed in a configured removedKeyCallback.
+     * If a celix_stringHashMap_put* function call returns false, a value is replaced and the already existing
+     * key is reused. If the needed the caller should free the provided key.

Review Comment:
   This consideration also applies to `celix_stringHashMap_remove` and `celix_stringHashMap_clear`. 



##########
libs/utils/include/celix_string_hash_map.h:
##########
@@ -90,14 +94,37 @@ typedef struct celix_string_hash_map_create_options {
      * only the simpledRemoveCallback will be used.
      *
      * Default is NULL.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] removedKey The key of the value that was removed from the hash map.
+     *                       Note that the removedKey can still be in use if the a value entry for the same key is
+     *                       replaced, so this callback should not free the removedKey.
+     * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the
+     *                         hash map and can be freed.
      */
     void (*removedCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT;
 
+    /**
+     * @brief A removed key callback, which if provided will be called if a key is no longer used in the hash map.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] key The key that is no longer used in the hash map. if `storeKeysWeakly` was configured as true,
+     *                the key can be freed.
+     */
+    void (*removedKeyCallback)(void* data, char* key) CELIX_OPTS_INIT;
+
     /**
      * @brief If set to true, the string hash map will not make of copy of the keys and assumes
      * that the keys are in scope/memory for the complete lifecycle of the string hash map.
      *
-     * Note that this changes the default behaviour of the celix_stringHashMap_put* functions.
+     * When keys are stored weakly it is the caller responsibility to check the return value of
+     * celix_stringHashMap_put* function calls.
+     * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key
+     * should never be freed or freed in a configured removedKeyCallback.
+     * If a celix_stringHashMap_put* function call returns false, a value is replaced and the already existing
+     * key is reused. If the needed the caller should free the provided key.

Review Comment:
   Then what shall we do if the already existing key is reused? Now we have no way to know that. We could require removeKeyCallback to be set if keys are stored weakly, and trigger the callback to notify the caller in this case. Will it work?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1383337330


##########
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

Review Comment:
   Short properties optimization is a good idea. I am not sure whether the size should be set according to max values.  If this is enough for 80% usages, then I consider it a good choice. Given that celix_properties are used extensively and for various purposes other than service metadata, 128/256 may seem less scary.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1386156886


##########
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));

Review Comment:
   Error handling is missing.



##########
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);

Review Comment:
   `ftell` can fail with -1. Assigning it to size_t without check should be avoided.



##########
libs/utils/src/properties.c:
##########
@@ -117,69 +409,62 @@ static void parseLine(const char* line, celix_properties_t *props) {
     output = NULL;
     outputPos = 0;
 
-    //Ignore empty lines
+    // Ignore empty lines
     if (line[0] == '\n' && line[1] == '\0') {
         return;
     }
 
-    char *key = calloc(1, key_len);
-    char *value = calloc(1, value_len);
+    char* key = calloc(1, key_len);
+    char* value = calloc(1, value_len);
     key[0] = '\0';
     value[0] = '\0';
 
     while (line[linePos] != '\0') {
         if (line[linePos] == ' ' || line[linePos] == '\t') {
             if (output == NULL) {
-                //ignore
+                // ignore
                 linePos += 1;
                 continue;
             }
-        }
-        else {
+        } else {
             if (output == NULL) {
                 output = key;
             }
         }
         if (line[linePos] == '=' || line[linePos] == ':' || line[linePos] == '#' || line[linePos] == '!') {
             if (precedingCharIsBackslash) {
-                //escaped special character
+                // escaped special character
                 output[outputPos++] = line[linePos];
                 updateBuffers(&key, &value, &output, outputPos, &key_len, &value_len);

Review Comment:
   If we are unlucky, many memory allocations could follow.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);

Review Comment:
   If we add '\n' in the end, why shall we insist that `header` contains a newline?



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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);

Review Comment:
   IIRC, overflow/underflow is not checked in this function.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL);
 }
 
-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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL);
+}
+
+const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties,

Review Comment:
   I just notice that we have `getVersion` and `getAsVersion`, but we don't have `getLong`, `getBool` and `getDouble`. Why?



##########
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);

Review Comment:
   This is an error. We'd better return NULL, and ERR should be used.



##########
libs/utils/src/properties.c:
##########
@@ -117,69 +409,62 @@ static void parseLine(const char* line, celix_properties_t *props) {
     output = NULL;
     outputPos = 0;
 
-    //Ignore empty lines
+    // Ignore empty lines
     if (line[0] == '\n' && line[1] == '\0') {
         return;
     }
 
-    char *key = calloc(1, key_len);
-    char *value = calloc(1, value_len);
+    char* key = calloc(1, key_len);

Review Comment:
   Need to handle memory allocation error.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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");

Review Comment:
   `errno` might provide more useful information.



##########
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;
+    }
 

Review Comment:
   Shall we protect against ridiculously large file?



##########
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);

Review Comment:
   Return value is not checked.



##########
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) {

Review Comment:
   This will call `strlen` again and again: https://godbolt.org/z/vocWKaTds



##########
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 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_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);
+        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 void celix_properties_parseLine(const char* line, celix_properties_t* props) {

Review Comment:
   Do we have some informal specification for this serialization? 
   
   If so, I think we need to rewrite it to address the main drawback of the current inefficiencies: for some properties with long keys and values, parsing it may lead to many memory allocations, which this PR tries to avoid. 
   
   I will advertise for the `manifest_readFromStream` approach: only one explicit memory allocation and one simple go of byte-by-byte in-place processing. Combined with short string optimization in hand, we will have a pretty efficient implementation. Moreover, such implementation is more robust than the current one. Error handling will be quite straightforward.
   
   Before we do that, a spec is needed. An informal one is more than enough.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL);
 }
 
-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);

Review Comment:
   Overflow/underflow not checked.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL);
 }
 
-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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL);
+}
+
+const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties,
+                                                   const char* key,
+                                                   const celix_version_t* defaultValue) {
+    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* properties, const char* key, const celix_version_t* version) {
+    return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version));
 }
 
-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;
+void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) {
+    celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version);
+}
+
+int celix_properties_size(const celix_properties_t* properties) {
+    return (int)celix_stringHashMap_size(properties->map);

Review Comment:
   We could change the return type to `size_t`. Conversion is not needed any more.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL);
 }
 
-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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL);
+}
+
+const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties,
+                                                   const char* key,
+                                                   const celix_version_t* defaultValue) {
+    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* properties, const char* key, const celix_version_t* version) {
+    return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version));
 }
 
-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;
+void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) {

Review Comment:
   `key` will be copied. 
   Will our users confuse it with `celix_properties_setWithoutCopy`? In that case, `key` will NOT be copied.



##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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 && 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->value);
         }
-        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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, &value, NULL, NULL, NULL);
+}
+
+double celix_properties_getAsDouble(const celix_properties_t* props, const char* key, double defaultValue) {
+    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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, &val, NULL, NULL);
 }
 
-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) {
+    return celix_properties_createAndSetEntry(props, key, NULL, NULL, NULL, &val, NULL);
+}
+
+const celix_version_t* celix_properties_getVersion(const celix_properties_t* properties,
+                                                   const char* key,
+                                                   const celix_version_t* defaultValue) {
+    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* properties, const char* key, const celix_version_t* version) {
+    return celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, celix_version_copy(version));
 }
 
-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;
+void celix_properties_setVersionWithoutCopy(celix_properties_t* properties, const char* key, celix_version_t* version) {
+    celix_properties_createAndSetEntry(properties, key, NULL, NULL, NULL, NULL, version);
+}
+
+int celix_properties_size(const celix_properties_t* properties) {
+    return (int)celix_stringHashMap_size(properties->map);
+}
+
+bool celix_properties_equals(const celix_properties_t* props1, const celix_properties_t* props2) {
+    if (props1 == NULL && props2 == NULL) {
+        return true;
+    }
+    if (props1 == NULL || props2 == NULL) {
+        return false;
+    }
+    if (celix_properties_size(props1) != celix_properties_size(props2)) {
+        return false;
+    }
+    CELIX_PROPERTIES_ITERATE(props1, iter) {
+        celix_properties_entry_t* entry2 = celix_properties_getEntry(props2, iter.key);
+        if (entry2 == NULL || !celix_properties_entryEquals(&iter.entry, entry2)) {
+            return false;
         }
     }
-    return 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_BUILD_ASSERT(sizeof(celix_properties_iterator_internal_t) <= sizeof(celix_properties_iterator_t));

Review Comment:
   This assertion does not guarantee that `memcpy(iter._data, &internalIter, sizeof(internalIter))` is safe. This does not take the offset of `_data` into account.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390163874


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,509 @@
  * 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 <stdio.h>
-#include <stdbool.h>
 
 #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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief The current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] filename The name of the file to load properties from.
+ * @return A property set containing the properties from the file.
+ * @retval NULL If an error occurred (e.g. file not found).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filename);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties,
+                                                                  char* key,
+                                                                  char* value);
 
-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 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);
 
-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 Set the value of a property to a long integer.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 Set the value of a property to a boolean.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] val The boolean value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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 int celix_properties_size(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property to a double.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 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);
 
-#define CELIX_PROPERTIES_FOR_EACH(props, key) _CELIX_PROPERTIES_FOR_EACH(CELIX_UNIQUE_ID(iter), props, key)
+/**
+ * @brief Set the value of a property as a Celix version.
+ *
+ * This function will make a copy of the provided celix_version_t object and store it in the property set.
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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);
 
-#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 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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] version The value to set. The function will store a reference to this object in the property set and
+ *                    takes ownership of the provided version.
+ *

Review Comment:
   @return is missing



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398054410


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,278 @@ 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[20];

Review Comment:
   ```suggestion
       char convertedValueBuffer[21];
   ```
   One extra byte for the null character.
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398088103


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,278 @@ 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];
+    if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = prototype->typed.versionValue;
+        bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(prototype->typed.versionValue);
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = prototype->typed.longValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written < 0 || written <= sizeof(convertedValueBuffer)) {

Review Comment:
   ```suggestion
           if (written >= 0 || written < sizeof(convertedValueBuffer)) {
   ```
   `written < 0 || written == sizeof(convertedValueBuffer)` should be error. According to `man snprintf`, " a return value of size or more means that the output was truncated".



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1391103653


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;

Review Comment:
   I agree that "the index of the end iterator is the same as the size of the map". 
   The current implementation of `celix_stringHashMapIterator_next` allow `iter->index > map->genericMap.size`. Is it intended?



-- 
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-unsubscribe@celix.apache.org

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


[GitHub] [celix] PengZheng commented on pull request #470: [RFC] type support for properties

Posted by GitBox <gi...@apache.org>.
PengZheng commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1380148122

   Nice! I'll have a detailed look at this. 


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1776990385

   This PR is now ready for review. 
   As noted earlier this PR has breaking changes, so the PR was on hold until Apache Celix 2.4.0 was released.
   
   Some additional changes added based on the previous review comments:
    - Updated celix_string/long_hash_map error handling, including a `celix_status_t` return for the `put` functions. Note that before this change the `put` functions returned the previous entry and now returns a status to indicate whether the `put` was successful. 
    - Update properties `set`  function to return a `celix_status_t` (this was a `void`).
    - Added error injection test to check the error handling of hash map put and create, celix properties create, set, store and load.
    - Added `equals` function for string/long hash map en properties
    - Removed the deprecated iterator functions and macro.
   
   There is still some work todo for storing, encoding any maybe arrays handling for properties but IMO this PR is already big enough so I created a separate issues for this: #674 .
   


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1397750197


##########
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:
   Thanks, a prototype is indeed much cleaner. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398119159


##########
libs/utils/src/properties.c:
##########
@@ -103,12 +131,278 @@ 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];
+    if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_VERSION;
+        entry->typed.versionValue = prototype->typed.versionValue;
+        bool written = celix_version_fillString(prototype->typed.versionValue, convertedValueBuffer, sizeof(convertedValueBuffer));
+        if (written) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            entry->value = celix_version_toString(prototype->typed.versionValue);
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_LONG;
+        entry->typed.longValue = prototype->typed.longValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%li", entry->typed.longValue);
+        if (written >= 0 || written < sizeof(convertedValueBuffer)) {
+            entry->value = celix_properties_createString(properties, convertedValueBuffer);
+        } else {
+            //LCOV_EXCL_START
+            assert(false); // should not happen, LONG_MAX str is 19 chars, LONG_MIN str is 20 chars
+            //LCOV_EXCL_STOP
+        }
+    } else if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_DOUBLE;
+        entry->typed.doubleValue = prototype->typed.doubleValue;
+        int written = snprintf(convertedValueBuffer, sizeof(convertedValueBuffer), "%f", entry->typed.doubleValue);
+        if (written < 0 || written <= sizeof(convertedValueBuffer)) {
+            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->valueType = CELIX_PROPERTIES_VALUE_TYPE_BOOL;
+        entry->typed.boolValue = prototype->typed.boolValue;
+        entry->value = entry->typed.boolValue ? CELIX_PROPERTIES_BOOL_TRUE_STRVAL : CELIX_PROPERTIES_BOOL_FALSE_STRVAL;
+    } else /*string value*/ {
+        assert(prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING);
+        entry->valueType = CELIX_PROPERTIES_VALUE_TYPE_STRING;
+        entry->value = celix_properties_createString(properties, prototype->typed.strValue);
+        entry->typed.strValue = entry->value;
+    }
+
+    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;
+}
+
+/**
+ * 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");
+        if (prototype->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+            celix_version_destroy((celix_version_t*)prototype->typed.versionValue);
+        }
+        if (entry >= properties->entriesBuffer &&
+            entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+            // entry is part of the properties entries buffer -> decrease the currentEntriesBufferIndex
+            properties->currentEntriesBufferIndex -= 1;
+        } else {
+            free(entry);
+        }
+        entry = NULL;
+    }
+    return entry;
+}
+
+static void celix_properties_destroyEntry(celix_properties_t* properties, celix_properties_entry_t* entry) {
+    celix_properties_freeString(properties, (char*)entry->value);
+    if (entry->valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION) {
+        celix_version_destroy((celix_version_t*)entry->typed.versionValue);
+    }
+
+    if (entry >= properties->entriesBuffer &&
+        entry <= (properties->entriesBuffer + CELIX_PROPERTIES_OPTIMIZATION_ENTRIES_BUFFER_SIZE)) {
+        // entry is part of the properties entries buffer -> nop.
+    } else {
+        free(entry);
+    }
+}
+
+/**
+ * Create and add entry and optionally use the short properties optimization buffers.
+ * The prototype is used to determine the type of the value.
+ */
+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) {

Review Comment:
   We can avoid this if it were done in `celix_properties_setVersion`/`celix_properties_setVersionWithoutCopy`.
   
   The code duplication is minimal.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1817754005

   > Ah I see, no this was not intentionally and I think it is better to keep the max iterator index on map size. Refactor the index increment for this.
   
   From the following documentation, I get the original intention of `index`:
   
   ```C
   typedef struct celix_string_hash_map_iterator {
       size_t index;    /**< The index of the iterator. Starts at 0, ends at map size (so beyond the last element). When an
                           entry is removed, the index value is not changed. */
   
   ```
   
   And yes, `celix_stringHashMapIterator_next allows iter->index > map->genericMap.size` is intentionally allowed.
   I noticed the newly added test `TEST_F(HashMapTestSuite, IterateWithRemoveAtIndex4Test)` seems weird:
   
   ```C
       celix_autoptr(celix_string_hash_map_t) sMap = createStringHashMap(6);
       auto iter1 = celix_stringHashMap_begin(sMap);
       while (!celix_stringHashMapIterator_isEnd(&iter1)) {
           if (iter1.index == 4) {
               // note only removing entries where the iter key is even
               celix_stringHashMapIterator_remove(&iter1);
           } else {
               celix_stringHashMapIterator_next(&iter1);
           }
       }
       EXPECT_EQ(4, celix_stringHashMap_size(sMap)); 
   ```
   
   4 rather than 5 entries remain.


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "xuzhenbao (via GitHub)" <gi...@apache.org>.
xuzhenbao commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378321550


##########
bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c:
##########
@@ -425,6 +424,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno
                     break;
                 }
                 TXTRecordDeallocate(&txtRecord);
+                celix_propertiesIterator_next(&propIter);

Review Comment:
   I think `celix_propertiesIterator_next` also should be invoked before `while (!celix_propertiesIterator_isEnd(&propIter))` . otherwise, a same property is set  in `txtRecord`.
   
   We can resolve it in another way: Only let `discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord` do `celix_propertiesIterator_next` whether txtRecord is filled or not.
   ~~~
   static bool discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord(discovery_zeroconf_announcer_t *announcer, celix_properties_iterator_t *propIter, TXTRecordRef *txtRecord, uint16_t maxTxtLen, bool splitTxtRecord) {
       const char *key;
       const char *val;
       while (!celix_propertiesIterator_isEnd(propIter)) {
           key = propIter->key;
           val = propIter->entry.value;
           if (key) {
               DNSServiceErrorType err = TXTRecordSetValue(txtRecord, key, strlen(val), val);
               if (err != kDNSServiceErr_NoError) {
                   celix_logHelper_error(announcer->logHelper, "Announcer: Failed to set txt value, %d.", err);
                   return false;
               }
               if (splitTxtRecord && TXTRecordGetLength(txtRecord) >= maxTxtLen - UINT8_MAX) {
                   celix_propertiesIterator_next(propIter);
                   break;
               }
           }
           celix_propertiesIterator_next(propIter);
       }
       return true;
   }
   ~~~



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390184329


##########
libs/utils/src/properties.c:
##########
@@ -191,324 +481,449 @@ 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+");
 
-    char *in = strdup(input);
-    char *line = NULL;
-    char *saveLinePointer = NULL;
+    if (file == NULL) {
+        celix_err_pushf("Cannot open file '%s'", filename);
+        return CELIX_FILE_IO_EXCEPTION;
+    }
+
+    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) {
+            rc = fputs(header, file);
+        }
+        if (rc != 0) {
+            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 celix_steal_ptr(copy);

Review Comment:
   ```suggestion
       return copy;
   ```
   copy is not an auto variable.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382402803


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or

Review Comment:
   `key ` is actually moved into properties. In case of error, it is freed. Thus it should not be touched after this function returns. Warning that "it must not be freed or modified after calling this function is not enough".



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1383324230


##########
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.

Review Comment:
   This note should apply to `entryBuffer`?



##########
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);

Review Comment:
   Return value of `asprintf` should allways be checked. This remark appies to several places.



##########
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

Review Comment:
   Short properties optimization is a good idea. I am not sure whether the size should be set according to max values.  If this is enough for 80% usages, then I consider it a good choice. Given that celix_properties are used extensively and for various purposes other than service metadata, 128/256 may seems less scary.



##########
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);

Review Comment:
   Retrun value should be checked.



##########
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.

Review Comment:
   This note should apply to `stringBuffer`?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390457651


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   Here are the load factor benchmark results (on my machine, using a release build type and cpupower on performance):
   
   # Hash3 , max load 0.75
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.421 ns        0.421 ns   1000000000 items_per_second=2.37362G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.421 ns        0.421 ns   1000000000 items_per_second=2.37341G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.422 ns        0.422 ns   1000000000 items_per_second=2.36961G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.421 ns        0.421 ns   1000000000 items_per_second=2.37266G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.422 ns        0.422 ns   1000000000 items_per_second=2.3715G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.422 ns        0.422 ns   1000000000 items_per_second=2.3694G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.32 ns         2.32 ns    302202596 averageNrOfEntriesPerBucket=0.625 items_per_second=431.121M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.58 ns         2.58 ns    272789300 averageNrOfEntriesPerBucket=0.390625 items_per_second=387.522M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=0.687322
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.37 ns         2.37 ns    295845710 averageNrOfEntriesPerBucket=0.488281 items_per_second=421.411M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=0.695872
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.39 ns         2.39 ns    294741774 averageNrOfEntriesPerBucket=0.610352 items_per_second=417.892M/s nrOfBuckets=16.384k resizeCount=10 stdDeviationNrOfEntriesPerBucket=0.784076
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.40 ns         2.40 ns    297425736 averageNrOfEntriesPerBucket=0.718061 items_per_second=417.292M/s nrOfBuckets=139.264k resizeCount=22 stdDeviationNrOfEntriesPerBucket=0.846435
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.27 ns         2.27 ns    308634832 averageNrOfEntriesPerBucket=0.747751 items_per_second=440.035M/s nrOfBuckets=1.33734M resizeCount=139 stdDeviationNrOfEntriesPerBucket=0.863872
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.53 ns         3.53 ns    198927031 items_per_second=283.682M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.52 ns         3.52 ns    198043432 items_per_second=283.711M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    195734925 items_per_second=283.213M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.53 ns         3.53 ns    197803895 items_per_second=283.436M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.52 ns         3.52 ns    198613048 items_per_second=284.045M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.50 ns         3.50 ns    199963559 items_per_second=285.326M/s
   ```
   
   # Hash3 , max load 3
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.425 ns        0.425 ns   1000000000 items_per_second=2.35061G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.426 ns        0.426 ns   1000000000 items_per_second=2.34699G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.426 ns        0.426 ns   1000000000 items_per_second=2.34825G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.426 ns        0.426 ns   1000000000 items_per_second=2.34747G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.426 ns        0.426 ns   1000000000 items_per_second=2.3473G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.437 ns        0.437 ns   1000000000 items_per_second=2.28811G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.34 ns         2.34 ns    299224720 averageNrOfEntriesPerBucket=0.625 items_per_second=428.114M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.76 ns         2.76 ns    252886933 averageNrOfEntriesPerBucket=1.5625 items_per_second=362.06M/s nrOfBuckets=64 resizeCount=2 stdDeviationNrOfEntriesPerBucket=1.26089
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.34 ns         2.34 ns    299386105 averageNrOfEntriesPerBucket=1.95312 items_per_second=428.21M/s nrOfBuckets=512 resizeCount=5 stdDeviationNrOfEntriesPerBucket=1.47033
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.55 ns         2.55 ns    274628326 averageNrOfEntriesPerBucket=2.44141 items_per_second=392.449M/s nrOfBuckets=4.096k resizeCount=8 stdDeviationNrOfEntriesPerBucket=1.56031
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.34 ns         2.34 ns    299777246 averageNrOfEntriesPerBucket=2.71267 items_per_second=428.166M/s nrOfBuckets=36.864k resizeCount=12 stdDeviationNrOfEntriesPerBucket=1.64409
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.33 ns         2.33 ns    299923251 averageNrOfEntriesPerBucket=2.99559 items_per_second=428.295M/s nrOfBuckets=333.824k resizeCount=41 stdDeviationNrOfEntriesPerBucket=1.73026
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.63 ns         3.63 ns    192852998 items_per_second=275.666M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.63 ns         3.63 ns    192824656 items_per_second=275.619M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.63 ns         3.63 ns    192825416 items_per_second=275.698M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.63 ns         3.63 ns    192885922 items_per_second=275.674M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.63 ns         3.63 ns    193014477 items_per_second=275.727M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.66 ns         3.66 ns    191065573 items_per_second=272.882M/s
   ```
   
   # Hash3, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.414 ns        0.414 ns   1000000000 items_per_second=2.4128G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.414 ns        0.414 ns   1000000000 items_per_second=2.41719G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.414 ns        0.414 ns   1000000000 items_per_second=2.41582G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.414 ns        0.414 ns   1000000000 items_per_second=2.41681G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.414 ns        0.414 ns   1000000000 items_per_second=2.41521G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.414 ns        0.414 ns   1000000000 items_per_second=2.41732G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.26 ns         2.26 ns    307909253 averageNrOfEntriesPerBucket=0.625 items_per_second=441.585M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.39 ns         3.39 ns    211113789 averageNrOfEntriesPerBucket=3.125 items_per_second=295.319M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.86665
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.48 ns         2.48 ns    281288298 averageNrOfEntriesPerBucket=3.90625 items_per_second=403.557M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.16123
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.31 ns         3.31 ns    211375732 averageNrOfEntriesPerBucket=4.88281 items_per_second=302.161M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.2035
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.48 ns         2.48 ns    282405576 averageNrOfEntriesPerBucket=3.75601 items_per_second=403.207M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.92598
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.90 ns         2.90 ns    241719478 averageNrOfEntriesPerBucket=4.98246 items_per_second=344.381M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23011
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.54 ns         3.54 ns    197602959 items_per_second=282.865M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.54 ns         3.53 ns    197704646 items_per_second=282.885M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    197684458 items_per_second=282.891M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.54 ns         3.54 ns    197731536 items_per_second=282.88M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.54 ns         3.54 ns    197896830 items_per_second=282.817M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.54 ns         3.54 ns    198110019 items_per_second=282.856M/s
   ```
   
   # Hash3, max load 10
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.435 ns        0.435 ns   1000000000 items_per_second=2.29977G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.432 ns        0.432 ns   1000000000 items_per_second=2.31494G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.426 ns        0.426 ns   1000000000 items_per_second=2.34589G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.426 ns        0.426 ns   1000000000 items_per_second=2.34525G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.426 ns        0.426 ns   1000000000 items_per_second=2.34972G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.425 ns        0.425 ns   1000000000 items_per_second=2.35186G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.33 ns         2.33 ns    299579738 averageNrOfEntriesPerBucket=0.625 items_per_second=428.363M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.57 ns         2.57 ns    274525405 averageNrOfEntriesPerBucket=6.25 items_per_second=388.373M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=2.53722
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               3.03 ns         3.03 ns    219679035 averageNrOfEntriesPerBucket=7.8125 items_per_second=329.912M/s nrOfBuckets=128 resizeCount=3 stdDeviationNrOfEntriesPerBucket=3.12687
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              8.02 ns         8.02 ns     81140813 averageNrOfEntriesPerBucket=9.76562 items_per_second=124.642M/s nrOfBuckets=1024 resizeCount=6 stdDeviationNrOfEntriesPerBucket=3.15172
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.55 ns         2.55 ns    274930765 averageNrOfEntriesPerBucket=6.10352 items_per_second=392.633M/s nrOfBuckets=16.384k resizeCount=10 stdDeviationNrOfEntriesPerBucket=2.46868
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            3.18 ns         3.18 ns    220016340 averageNrOfEntriesPerBucket=9.21285 items_per_second=313.99M/s nrOfBuckets=108.544k resizeCount=19 stdDeviationNrOfEntriesPerBucket=3.02688
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.62 ns         3.62 ns    193278219 items_per_second=276.196M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.62 ns         3.62 ns    193216614 items_per_second=276.198M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.62 ns         3.62 ns    193269813 items_per_second=276.076M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.62 ns         3.62 ns    193179709 items_per_second=276.217M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.62 ns         3.62 ns    193208642 items_per_second=276.177M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.62 ns         3.62 ns    193566533 items_per_second=276.364M/s
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390191593


##########
libs/utils/src/version.c:
##########
@@ -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");

Review Comment:
   Usage of ERR should be documented.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1399944798


##########
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;
+    }
 

Review Comment:
   > Also if we add a max properties file size, I think this should be configurable with a conan/cmake build option.
   I think we can deal with it later when necessary.



##########
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;
+    }
 

Review Comment:
   > Also if we add a max properties file size, I think this should be configurable with a conan/cmake build option.
   
   I think we can deal with it later when necessary.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389375314


##########
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:
   Updated to return CELIX_ILLEGAL_ARGUMENT status 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1391103653


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;

Review Comment:
   I agree that "the index of the end iterator is the same as the size of the map". 
   The current implementation of `celix_stringHashMapIterator_next` allows `iter->index > map->genericMap.size`. Is it intended?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378446643


##########
libs/rcm/src/celix_capability.c:
##########
@@ -83,22 +83,19 @@ bool celix_capability_equals(const celix_capability_t* cap1, const celix_capabil
 
     //compare attributes
     bool equals = true;
-    const char* visit;
-    CELIX_PROPERTIES_FOR_EACH(cap1->attributes, visit) {
-        const char* value1 = celix_properties_get(cap1->attributes, visit, NULL);
-        const char* value2 = celix_properties_get(cap2->attributes, visit, NULL);
-        if (!celix_utils_stringEquals(value1, value2)) {
+    CELIX_PROPERTIES_ITERATE(cap1->attributes, visit) {

Review Comment:
   Shall we use `celix_properties_equals` instead? 



##########
libs/utils/gtest/src/CxxPropertiesTestSuite.cc:
##########
@@ -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_EQ(props.size(), loadedProps.size());

Review Comment:
   If #674 were solved, this should be equality test.



##########
libs/rcm/src/celix_requirement.c:
##########
@@ -89,45 +89,33 @@ bool celix_requirement_equals(const celix_requirement_t* req1, const celix_requi
     }
 
     //compare attributes
-    bool equals = true;
-    const char* visit;
-    CELIX_PROPERTIES_FOR_EACH(req1->attributes, visit) {
-        const char* val1 = celix_properties_get(req1->attributes, visit, NULL);
-        const char* val2 = celix_properties_get(req2->attributes, visit, NULL);
-        if (!celix_utils_stringEquals(val1, val2)) {
-            equals = false;
-            break;
+    CELIX_PROPERTIES_ITERATE(req1->attributes, visit) {

Review Comment:
   Shall we use `celix_properties_equals` instead? 



##########
libs/utils/gtest/src/HashMapTestSuite.cc:
##########
@@ -534,25 +514,159 @@ TEST_F(HashMapTestSuite, IterateWithRemoveTest) {
     auto iter2 = celix_longHashMap_begin(lMap);
     while (!celix_longHashMapIterator_isEnd(&iter2)) {
         if (iter2.index % 2 == 0) {
-            //note only removing entries where the iter index is even
+            // note only removing entries where the iter index is even
             celix_longHashMapIterator_remove(&iter2);
         } else {
             celix_longHashMapIterator_next(&iter2);
         }
     }
     EXPECT_EQ(3, celix_longHashMap_size(lMap));
     EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2));
-    celix_longHashMapIterator_next(&iter2); //calling next on end iter, does nothing
+    celix_longHashMapIterator_next(&iter2); // calling next on end iter, does nothing
     EXPECT_TRUE(celix_longHashMapIterator_isEnd(&iter2));
     celix_longHashMap_destroy(lMap);
 }
 
-TEST_F(HashMapTestSuite, StringHashMapCleanup) {
+TEST_F(HashMapTestSuite, IterateEndTest) {
+    auto* sMap1 = createStringHashMap(0);
+    auto* sMap2 = createStringHashMap(6);
+    auto* lMap1 = createLongHashMap(0);
+    auto* lMap2 = createLongHashMap(6);
+
+    auto sIter1 = celix_stringHashMap_end(sMap1);
+    auto sIter2 = celix_stringHashMap_end(sMap2);
+    auto lIter1 = celix_longHashMap_end(lMap1);
+    auto lIter2 = celix_longHashMap_end(lMap2);
+
+    EXPECT_EQ(sIter1.index, 0);
+    EXPECT_EQ(sIter2.index, 6);
+    EXPECT_EQ(lIter1.index, 0);
+    EXPECT_EQ(lIter2.index, 6);
+    EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter1));
+    EXPECT_TRUE(celix_stringHashMapIterator_isEnd(&sIter2));
+    EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter1));
+    EXPECT_TRUE(celix_longHashMapIterator_isEnd(&lIter2));
+
+    celix_stringHashMap_destroy(sMap1);
+    celix_stringHashMap_destroy(sMap2);
+    celix_longHashMap_destroy(lMap1);
+    celix_longHashMap_destroy(lMap2);
+}
+
+TEST_F(HashMapTestSuite, EqualsTest) {
+    auto* sMap = createStringHashMap(2);
+    auto sIter1 = celix_stringHashMap_begin(sMap);
+    auto sIter2 = celix_stringHashMap_begin(sMap);
+
+    // Test equal iterators
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Test unequal iterators after only 1 modification
+    celix_stringHashMapIterator_next(&sIter1);
+    EXPECT_FALSE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Test equal iterators after both modification
+    celix_stringHashMapIterator_next(&sIter2);
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sIter2));
+
+    // Same for long hash map
+    auto* lMap = createLongHashMap(1);
+    auto lIter1 = celix_longHashMap_begin(lMap);
+    auto lIter2 = celix_longHashMap_begin(lMap);
+
+    // Test equal iterators
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    // Test unequal iterators after only 1 modification
+    celix_longHashMapIterator_next(&lIter1);
+    EXPECT_FALSE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    // Test equal iterators after both modification
+    celix_longHashMapIterator_next(&lIter2);
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lIter2));
+
+    celix_stringHashMap_destroy(sMap);
+    celix_longHashMap_destroy(lMap);
+}
+
+TEST_F(HashMapTestSuite, EqualsZeroSizeMapTest) {
+    // Because map size is 0, begin iter should equal end iter
+    auto* sMap = createStringHashMap(0);
+    auto sIter1 = celix_stringHashMap_begin(sMap);
+    auto sEnd = celix_stringHashMap_end(sMap);
+    EXPECT_TRUE(celix_stringHashMapIterator_equals(&sIter1, &sEnd));
+
+    // Because map size is 0, begin iter should equal end iter
+    auto* lMap = createLongHashMap(0);
+    auto lIter1 = celix_longHashMap_begin(lMap);
+    auto lEnd = celix_longHashMap_end(lMap);
+    EXPECT_TRUE(celix_longHashMapIterator_equals(&lIter1, &lEnd));
+
+    celix_stringHashMap_destroy(sMap);
+    celix_longHashMap_destroy(lMap);
+}
+
+TEST_F(HashMapTestSuite, StoreKeysWeaklyTest) {
+    celix_string_hash_map_create_options_t opts{};
+    opts.removedCallbackData = (void*)0x1;
+    opts.storeKeysWeakly = true;
+    opts.removedKeyCallback = [](void* data, char* key) {
+        EXPECT_EQ(data, (void*)0x1);
+        free(key);
+    };
+    auto* sMap = celix_stringHashMap_createWithOptions(&opts);
+    EXPECT_FALSE(celix_stringHashMap_putLong(sMap, celix_utils_strdup("key1"), 1)); // new key -> takes ownership

Review Comment:
   `celix_stringHashMap_putXXX` no longer returns boolean value. 
   Documentation of `opts.removedKeyCallback` should also be updated.



##########
libs/utils/gtest/src/CxxPropertiesTestSuite.cc:
##########
@@ -52,14 +52,37 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) {
     EXPECT_EQ(props.getAsBool("key5", false), true);
 
     int count = 0;
+    for (auto it = props.begin(); it != props.end(); ++it) {

Review Comment:
   Is this equivalent to the following range-based loop? Why shall we need both?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1375760300


##########
libs/utils/include/celix/Version.h:
##########
@@ -0,0 +1,175 @@
+/*
+ * 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 <memory>
+#include <string>
+
+#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.
+         */
+#if __cplusplus >= 201703L //C++17 or higher

Review Comment:
   Remove C++17 string_view usage



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1377595646


##########
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;

Review Comment:
   Shall we check whether `val == NULL`?



##########
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, "");

Review Comment:
   Why not `iter.entry.value`?  Maybe the value could be `NULL`?
   If value could be `NULL`, we need to check all previous usages of `iter.entry.value`.



##########
bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c:
##########
@@ -101,19 +101,17 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t* message,
             *bufferInOut = newBuffer;
             *bufferLengthInOut = newLength;
         }
-        const char* key;
         if (metadataSize == 0) {
             encoded = true;
             continue;
         }
         celix_status_t status = CELIX_SUCCESS;
-        CELIX_PROPERTIES_FOR_EACH(message->metadata.metadata, key) {
-            const char *val = celix_properties_get(message->metadata.metadata, key, "");
+        CELIX_PROPERTIES_ITERATE(message->metadata.metadata, iter) {
             if (status == CELIX_SUCCESS) {
-                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, key);
+                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, iter.key);

Review Comment:
   Previously, it was guaranteed that `val != NULL`. Now does this invariant still hold?



##########
bundles/remote_services/discovery_zeroconf/src/discovery_zeroconf_announcer.c:
##########
@@ -425,6 +424,7 @@ static void discoveryZeroconfAnnouncer_announceEndpoints(discovery_zeroconf_anno
                     break;
                 }
                 TXTRecordDeallocate(&txtRecord);
+                celix_propertiesIterator_next(&propIter);

Review Comment:
   `discoveryZeroconfAnnouncer_copyPropertiesToTxtRecord` already calls `celix_propertiesIterator_next` once. @xuzhenbao please have a look at RSA.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1377963635


##########
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;

Review Comment:
   val cannot be null. When adding a property with a string value if NULL, a empty string ("") will be used instead using the celix_properties_createString function:
   ```
   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;
   }
   ```
   
   As result val cannot be NULL. I will update the properties iterator documentation to make this more explicit.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1377967375


##########
bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c:
##########
@@ -101,19 +101,17 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t* message,
             *bufferInOut = newBuffer;
             *bufferLengthInOut = newLength;
         }
-        const char* key;
         if (metadataSize == 0) {
             encoded = true;
             continue;
         }
         celix_status_t status = CELIX_SUCCESS;
-        CELIX_PROPERTIES_FOR_EACH(message->metadata.metadata, key) {
-            const char *val = celix_properties_get(message->metadata.metadata, key, "");
+        CELIX_PROPERTIES_ITERATE(message->metadata.metadata, iter) {
             if (status == CELIX_SUCCESS) {
-                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, key);
+                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, iter.key);

Review Comment:
   A value cannot be NULL (see previous comment) and a NULL value as key is also not accepted:
   ```
   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
       }
   
   ...
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1817518370

   > I agree that "the index of the end iterator is the same as the size of the map".
   The current implementation of celix_stringHashMapIterator_next allows iter->index > map->genericMap.size. Is it intended?
   
   Ah i see, no this was not intentionally and I think it is better to keep the max iterator index on map size. Refactor the index increment for this. 


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398332841


##########
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);

Review Comment:
   ```C
   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); // this return value is ignored.
       }
       return rc;
   }
   
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1397781840


##########
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:
   I think this is not true. The reason is that the celix_properties_freeString can be called because of a `celix_properties_unser` and in that case the to be "freed" string is not the last entry in string buffer. 
   
   Of course it is possible (with the pointer and len value) to move the rest of the string value earlier in the buffer, but IMO this adds to much complexity for these corner cases of fragmented properties optimized string buffer (ENOMEM or unset). 
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1397781840


##########
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:
   I think this is not true. The reason is that the celix_properties_freeString can be called because of a `celix_properties_unser` and in that case the freed string is not the last entry in string buffer. 
   
   Of course it is possible (with the pointer and len value) to move the rest of the string value earlier in the buffer, but IMO this adds to much complexity for these corner cases of fragmented properties optimized string buffer (ENOMEM or unset). 
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382402803


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or

Review Comment:
   `key ` is actually moved into properties. In case of error, it is freed. Thus it should not be touched after this function returns.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1804253059

   > Here are some remarks on properties implementation, and I have finished the first round review, which takes much longer than expected. That is understandable: for such a change of fundamental type used everywhere, we need to be extra careful to avoid introducing bugs. I tried my best.
   > 
   > Overall, it's a nice work which brings about huge performance and ergonomics improvements. Looking forward to getting it merged and implementing #674 thereafter.
   
   Thanks for the thorough review :smile: . I will update the implementation based on the review comments. 


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390163874


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,509 @@
  * 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 <stdio.h>
-#include <stdbool.h>
 
 #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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief The current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] filename The name of the file to load properties from.
+ * @return A property set containing the properties from the file.
+ * @retval NULL If an error occurred (e.g. file not found).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_load(const char* filename);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 CELIX_ENOMEM is returned, the key and value strings are not used and freed by this function.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties,
+                                                                  char* key,
+                                                                  char* value);
 
-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 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);
 
-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 Set the value of a property to a long integer.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 Set the value of a property to a boolean.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] val The boolean value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry
+ *         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 int celix_properties_size(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property to a double.
+ *
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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 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 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);
 
-#define CELIX_PROPERTIES_FOR_EACH(props, key) _CELIX_PROPERTIES_FOR_EACH(CELIX_UNIQUE_ID(iter), props, key)
+/**
+ * @brief Set the value of a property as a Celix version.
+ *
+ * This function will make a copy of the provided celix_version_t object and store it in the property set.
+ * If the return status is an error, a error message is logged to celix_err.
+ *
+ * @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);
 
-#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 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, a error message is logged to celix_err.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] version The value to set. The function will store a reference to this object in the property set and
+ *                    takes ownership of the provided version.
+ *

Review Comment:
   `@return` is missing.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1382392394


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.

Review Comment:
   ```suggestion
        * @brief The current key.
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1399584425


##########
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;
+    }
 

Review Comment:
   I think this is wise, but what is a good max properties file size?
   
   Also if we add a max properties file size, I think this should be configurable with a conan/cmake build option. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390973000


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -19,64 +19,56 @@
 
 #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 <stdlib.h>
 #include <memory.h>
 #include <math.h>
 #include <assert.h>
 #include <stdint.h>
 
-/**
- * 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;
     celix_hash_map_entry_t* next;
     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 modificationsCount;
+    size_t resizeCount;
+    size_t collisionsCount;

Review Comment:
   `modificationsCount` and `collisionsCount` are unused now.



##########
libs/utils/include/celix_long_hash_map.h:
##########
@@ -105,21 +114,21 @@ typedef struct celix_long_hash_map_create_options {
     unsigned int initialCapacity CELIX_OPTS_INIT;
 
     /**
-     * @brief The hash map load factor, which controls the max ratio between nr of entries in the hash map and the
+     * @brief The hash map max load factor, which controls the max ratio between nr of entries in the hash map and the
      * hash map capacity.
      *
-     * The load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries
+     * The max load factor controls how large the hash map capacity (nr of buckets) is compared to the nr of entries
      * in the hash map. The load factor is an important property of the hash map which influences how close the
      * hash map performs to O(1) for its get, has and put operations.
      *
-     * If the nr of entries increases above the loadFactor * capacity, the hash capacity will be doubled.
+     * If the nr of entries increases above the maxLoadFactor * capacity, the hash table capacity will be doubled.
      * For example a hash map with capacity 16 and load factor 0.75 will double its capacity when the 13th entry
      * is added to the hash map.
      *
      * If 0 is provided, the hash map load factor will be 0.75 (default hash map load factor).

Review Comment:
   ```suggestion
        * If 0 is provided, the hash map load factor will be 5 (default hash map load factor).
   ```



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;

Review Comment:
   I agree that "the index of the end iterator is the same as the size of the map". 
   The current implementation of `celix_stringHashMapIterator_next` allow `iter->index > map->genericMap.size`. Is is intended?



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390457651


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   Here are the load factor benchmark result (on my machine, using a release build type and cpupower on performance):
   
   # Hash3 , max load 0.75
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.421 ns        0.421 ns   1000000000 items_per_second=2.37362G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.421 ns        0.421 ns   1000000000 items_per_second=2.37341G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.422 ns        0.422 ns   1000000000 items_per_second=2.36961G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.421 ns        0.421 ns   1000000000 items_per_second=2.37266G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.422 ns        0.422 ns   1000000000 items_per_second=2.3715G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.422 ns        0.422 ns   1000000000 items_per_second=2.3694G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.32 ns         2.32 ns    302202596 averageNrOfEntriesPerBucket=0.625 items_per_second=431.121M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.58 ns         2.58 ns    272789300 averageNrOfEntriesPerBucket=0.390625 items_per_second=387.522M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=0.687322
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.37 ns         2.37 ns    295845710 averageNrOfEntriesPerBucket=0.488281 items_per_second=421.411M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=0.695872
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.39 ns         2.39 ns    294741774 averageNrOfEntriesPerBucket=0.610352 items_per_second=417.892M/s nrOfBuckets=16.384k resizeCount=10 stdDeviationNrOfEntriesPerBucket=0.784076
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.40 ns         2.40 ns    297425736 averageNrOfEntriesPerBucket=0.718061 items_per_second=417.292M/s nrOfBuckets=139.264k resizeCount=22 stdDeviationNrOfEntriesPerBucket=0.846435
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.27 ns         2.27 ns    308634832 averageNrOfEntriesPerBucket=0.747751 items_per_second=440.035M/s nrOfBuckets=1.33734M resizeCount=139 stdDeviationNrOfEntriesPerBucket=0.863872
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.53 ns         3.53 ns    198927031 items_per_second=283.682M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.52 ns         3.52 ns    198043432 items_per_second=283.711M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    195734925 items_per_second=283.213M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.53 ns         3.53 ns    197803895 items_per_second=283.436M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.52 ns         3.52 ns    198613048 items_per_second=284.045M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.50 ns         3.50 ns    199963559 items_per_second=285.326M/s
   ```
   
   # Hash3 , max load 3
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.425 ns        0.425 ns   1000000000 items_per_second=2.35061G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.426 ns        0.426 ns   1000000000 items_per_second=2.34699G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.426 ns        0.426 ns   1000000000 items_per_second=2.34825G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.426 ns        0.426 ns   1000000000 items_per_second=2.34747G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.426 ns        0.426 ns   1000000000 items_per_second=2.3473G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.437 ns        0.437 ns   1000000000 items_per_second=2.28811G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.34 ns         2.34 ns    299224720 averageNrOfEntriesPerBucket=0.625 items_per_second=428.114M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.76 ns         2.76 ns    252886933 averageNrOfEntriesPerBucket=1.5625 items_per_second=362.06M/s nrOfBuckets=64 resizeCount=2 stdDeviationNrOfEntriesPerBucket=1.26089
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.34 ns         2.34 ns    299386105 averageNrOfEntriesPerBucket=1.95312 items_per_second=428.21M/s nrOfBuckets=512 resizeCount=5 stdDeviationNrOfEntriesPerBucket=1.47033
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.55 ns         2.55 ns    274628326 averageNrOfEntriesPerBucket=2.44141 items_per_second=392.449M/s nrOfBuckets=4.096k resizeCount=8 stdDeviationNrOfEntriesPerBucket=1.56031
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.34 ns         2.34 ns    299777246 averageNrOfEntriesPerBucket=2.71267 items_per_second=428.166M/s nrOfBuckets=36.864k resizeCount=12 stdDeviationNrOfEntriesPerBucket=1.64409
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.33 ns         2.33 ns    299923251 averageNrOfEntriesPerBucket=2.99559 items_per_second=428.295M/s nrOfBuckets=333.824k resizeCount=41 stdDeviationNrOfEntriesPerBucket=1.73026
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.63 ns         3.63 ns    192852998 items_per_second=275.666M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.63 ns         3.63 ns    192824656 items_per_second=275.619M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.63 ns         3.63 ns    192825416 items_per_second=275.698M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.63 ns         3.63 ns    192885922 items_per_second=275.674M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.63 ns         3.63 ns    193014477 items_per_second=275.727M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.66 ns         3.66 ns    191065573 items_per_second=272.882M/s
   ```
   
   # Hash3, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.414 ns        0.414 ns   1000000000 items_per_second=2.4128G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.414 ns        0.414 ns   1000000000 items_per_second=2.41719G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.414 ns        0.414 ns   1000000000 items_per_second=2.41582G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.414 ns        0.414 ns   1000000000 items_per_second=2.41681G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.414 ns        0.414 ns   1000000000 items_per_second=2.41521G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.414 ns        0.414 ns   1000000000 items_per_second=2.41732G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.26 ns         2.26 ns    307909253 averageNrOfEntriesPerBucket=0.625 items_per_second=441.585M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.39 ns         3.39 ns    211113789 averageNrOfEntriesPerBucket=3.125 items_per_second=295.319M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.86665
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.48 ns         2.48 ns    281288298 averageNrOfEntriesPerBucket=3.90625 items_per_second=403.557M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.16123
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.31 ns         3.31 ns    211375732 averageNrOfEntriesPerBucket=4.88281 items_per_second=302.161M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.2035
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.48 ns         2.48 ns    282405576 averageNrOfEntriesPerBucket=3.75601 items_per_second=403.207M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.92598
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.90 ns         2.90 ns    241719478 averageNrOfEntriesPerBucket=4.98246 items_per_second=344.381M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23011
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.54 ns         3.54 ns    197602959 items_per_second=282.865M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.54 ns         3.53 ns    197704646 items_per_second=282.885M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    197684458 items_per_second=282.891M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.54 ns         3.54 ns    197731536 items_per_second=282.88M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.54 ns         3.54 ns    197896830 items_per_second=282.817M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.54 ns         3.54 ns    198110019 items_per_second=282.856M/s
   ```
   
   # Hash3, max load 10
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.435 ns        0.435 ns   1000000000 items_per_second=2.29977G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.432 ns        0.432 ns   1000000000 items_per_second=2.31494G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.426 ns        0.426 ns   1000000000 items_per_second=2.34589G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.426 ns        0.426 ns   1000000000 items_per_second=2.34525G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.426 ns        0.426 ns   1000000000 items_per_second=2.34972G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.425 ns        0.425 ns   1000000000 items_per_second=2.35186G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.33 ns         2.33 ns    299579738 averageNrOfEntriesPerBucket=0.625 items_per_second=428.363M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.57 ns         2.57 ns    274525405 averageNrOfEntriesPerBucket=6.25 items_per_second=388.373M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=2.53722
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               3.03 ns         3.03 ns    219679035 averageNrOfEntriesPerBucket=7.8125 items_per_second=329.912M/s nrOfBuckets=128 resizeCount=3 stdDeviationNrOfEntriesPerBucket=3.12687
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              8.02 ns         8.02 ns     81140813 averageNrOfEntriesPerBucket=9.76562 items_per_second=124.642M/s nrOfBuckets=1024 resizeCount=6 stdDeviationNrOfEntriesPerBucket=3.15172
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.55 ns         2.55 ns    274930765 averageNrOfEntriesPerBucket=6.10352 items_per_second=392.633M/s nrOfBuckets=16.384k resizeCount=10 stdDeviationNrOfEntriesPerBucket=2.46868
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            3.18 ns         3.18 ns    220016340 averageNrOfEntriesPerBucket=9.21285 items_per_second=313.99M/s nrOfBuckets=108.544k resizeCount=19 stdDeviationNrOfEntriesPerBucket=3.02688
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.62 ns         3.62 ns    193278219 items_per_second=276.196M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.62 ns         3.62 ns    193216614 items_per_second=276.198M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.62 ns         3.62 ns    193269813 items_per_second=276.076M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.62 ns         3.62 ns    193179709 items_per_second=276.217M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.62 ns         3.62 ns    193208642 items_per_second=276.177M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.62 ns         3.62 ns    193566533 items_per_second=276.364M/s
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390458391


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   Here are the hash function benchmark results (on my machine, using a release build type and cpupower on performance):
   
   hash1 is the long hash function used in celix_long_hash_map at the start of this PR
   hash2 is the long hash function used in the deprecated hashmap 
   hash3 is a altered long hash function which uses a prime.
   
   I also had a short look into [murmur3](https://github.com/logrhythm/MurMurHash/blob/master/src/MurMurHash.cpp) and [FNV](https://github.com/fnvhash/libfnv/tree/master) hash, but for the moment I think this requires too much work (technical and license wise)
   
   # Hash1, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.432 ns        0.432 ns   1000000000 items_per_second=2.31246G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.433 ns        0.433 ns   1000000000 items_per_second=2.31041G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.433 ns        0.433 ns   1000000000 items_per_second=2.31151G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.434 ns        0.434 ns   1000000000 items_per_second=2.30551G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.433 ns        0.433 ns   1000000000 items_per_second=2.31037G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.433 ns        0.432 ns   1000000000 items_per_second=2.30713G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.38 ns         2.38 ns    293482551 averageNrOfEntriesPerBucket=0.625 items_per_second=420.077M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                2.64 ns         2.63 ns    268351618 averageNrOfEntriesPerBucket=3.125 items_per_second=379.474M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.91621
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.16 ns         2.16 ns    323130939 averageNrOfEntriesPerBucket=3.90625 items_per_second=462.94M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=1.80467
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.90 ns         3.89 ns    183397882 averageNrOfEntriesPerBucket=4.88281 items_per_second=256.477M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.20062
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.38 ns         2.38 ns    294279918 averageNrOfEntriesPerBucket=3.75601 items_per_second=420.113M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.93968
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.62 ns         2.62 ns    270684694 averageNrOfEntriesPerBucket=4.98246 items_per_second=380.976M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23419
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.67 ns         3.67 ns    190401851 items_per_second=272.19M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.69 ns         3.68 ns    190481474 items_per_second=271.21M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.69 ns         3.68 ns    189597715 items_per_second=271.084M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.69 ns         3.68 ns    189618122 items_per_second=271.035M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.69 ns         3.68 ns    189681903 items_per_second=271.169M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.67 ns         3.67 ns    190523011 items_per_second=272.179M/s
   ```
   
   
   # Hash 2, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.428 ns        0.428 ns   1000000000 items_per_second=2.33786G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.427 ns        0.427 ns   1000000000 items_per_second=2.34001G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.428 ns        0.428 ns   1000000000 items_per_second=2.33579G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.427 ns        0.427 ns   1000000000 items_per_second=2.3403G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.427 ns        0.427 ns   1000000000 items_per_second=2.34037G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.428 ns        0.428 ns   1000000000 items_per_second=2.33835G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.56 ns         2.56 ns    272918800 averageNrOfEntriesPerBucket=0.625 items_per_second=390.34M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.695971
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.76 ns         3.76 ns    186188393 averageNrOfEntriesPerBucket=3.125 items_per_second=266.203M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.93245
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.78 ns         2.78 ns    251731622 averageNrOfEntriesPerBucket=3.90625 items_per_second=359.86M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.00366
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              2.99 ns         2.99 ns    233585592 averageNrOfEntriesPerBucket=4.88281 items_per_second=333.894M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.14352
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             3.00 ns         3.00 ns    233887362 averageNrOfEntriesPerBucket=3.75601 items_per_second=333.838M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.94674
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.99 ns         2.99 ns    234075293 averageNrOfEntriesPerBucket=4.98246 items_per_second=334.062M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.22839
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.64 ns         3.64 ns    192084107 items_per_second=274.697M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.64 ns         3.64 ns    192187264 items_per_second=274.762M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.64 ns         3.64 ns    192202353 items_per_second=274.977M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.63 ns         3.63 ns    192491569 items_per_second=275.124M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.63 ns         3.63 ns    192638166 items_per_second=275.242M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.63 ns         3.63 ns    193028763 items_per_second=275.699M/s
   ```
   
   # Hash3, max load 5
   ```
   -----------------------------------------------------------------------------------------------------------------------------------------
   Benchmark                                                                               Time             CPU   Iterations UserCounters...
   -----------------------------------------------------------------------------------------------------------------------------------------
   LongHashmapBenchmark_findEntryFromStdMap/10/process_time/real_time                  0.414 ns        0.414 ns   1000000000 items_per_second=2.4128G/s
   LongHashmapBenchmark_findEntryFromStdMap/100/process_time/real_time                 0.414 ns        0.414 ns   1000000000 items_per_second=2.41719G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000/process_time/real_time                0.414 ns        0.414 ns   1000000000 items_per_second=2.41582G/s
   LongHashmapBenchmark_findEntryFromStdMap/10000/process_time/real_time               0.414 ns        0.414 ns   1000000000 items_per_second=2.41681G/s
   LongHashmapBenchmark_findEntryFromStdMap/100000/process_time/real_time              0.414 ns        0.414 ns   1000000000 items_per_second=2.41521G/s
   LongHashmapBenchmark_findEntryFromStdMap/1000000/process_time/real_time             0.414 ns        0.414 ns   1000000000 items_per_second=2.41732G/s
   LongHashmapBenchmark_findEntryFromCelixMap/10/process_time/real_time                 2.26 ns         2.26 ns    307909253 averageNrOfEntriesPerBucket=0.625 items_per_second=441.585M/s nrOfBuckets=16 resizeCount=0 stdDeviationNrOfEntriesPerBucket=0.856957
   LongHashmapBenchmark_findEntryFromCelixMap/100/process_time/real_time                3.39 ns         3.39 ns    211113789 averageNrOfEntriesPerBucket=3.125 items_per_second=295.319M/s nrOfBuckets=32 resizeCount=1 stdDeviationNrOfEntriesPerBucket=1.86665
   LongHashmapBenchmark_findEntryFromCelixMap/1000/process_time/real_time               2.48 ns         2.48 ns    281288298 averageNrOfEntriesPerBucket=3.90625 items_per_second=403.557M/s nrOfBuckets=256 resizeCount=4 stdDeviationNrOfEntriesPerBucket=2.16123
   LongHashmapBenchmark_findEntryFromCelixMap/10000/process_time/real_time              3.31 ns         3.31 ns    211375732 averageNrOfEntriesPerBucket=4.88281 items_per_second=302.161M/s nrOfBuckets=2.048k resizeCount=7 stdDeviationNrOfEntriesPerBucket=2.2035
   LongHashmapBenchmark_findEntryFromCelixMap/100000/process_time/real_time             2.48 ns         2.48 ns    282405576 averageNrOfEntriesPerBucket=3.75601 items_per_second=403.207M/s nrOfBuckets=26.624k resizeCount=11 stdDeviationNrOfEntriesPerBucket=1.92598
   LongHashmapBenchmark_findEntryFromCelixMap/1000000/process_time/real_time            2.90 ns         2.90 ns    241719478 averageNrOfEntriesPerBucket=4.98246 items_per_second=344.381M/s nrOfBuckets=200.704k resizeCount=28 stdDeviationNrOfEntriesPerBucket=2.23011
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10/process_time/real_time            3.54 ns         3.54 ns    197602959 items_per_second=282.865M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100/process_time/real_time           3.54 ns         3.53 ns    197704646 items_per_second=282.885M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000/process_time/real_time          3.53 ns         3.53 ns    197684458 items_per_second=282.891M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/10000/process_time/real_time         3.54 ns         3.54 ns    197731536 items_per_second=282.88M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/100000/process_time/real_time        3.54 ns         3.54 ns    197896830 items_per_second=282.817M/s
   LongHashmapBenchmark_findEntryFromDeprecatedMap/1000000/process_time/real_time       3.54 ns         3.54 ns    198110019 items_per_second=282.856M/s
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390457541


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -339,21 +327,27 @@ static bool celix_hashMap_remove(celix_hash_map_t* map, const char* strKey, long
         visit = visit->next;
     }
     if (removedEntry != NULL) {
+        char* removedKey = NULL;
+        if (map->keyType == CELIX_HASH_MAP_STRING_KEY) {
+            removedKey = (char*)removedEntry->key.strKey;
+        }
         celix_hashMap_destroyRemovedEntry(map, removedEntry);
+        if (removedKey) {
+            celix_hashMap_destroyRemovedKey(map, removedKey);
+        }
         return true;
     }
     return false;
 }
 
-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;

Review Comment:
   Interesting. The load factor of 0.75 was and still is used in the deprecated hash map. I did reading up on hash maps when I created the long/string hash map, but apparently missed this and reused the 0.75 factor. 
   
   I spent some hours tweaking the max load factor and hash functions combined with the already existing hash map benchmarks. Based on that result I changed the default long hash, refactored some code to prevent some function calls and changed the max load factor. 
   
   Max load factor is now 5.0 and the long hash function is also changed (uses a prime number for an overall better distribution in the hash map).
   
   I quite certain more improvements are possible for the long hash map and this can also been seen in the benchmark reference (`std::unordered_map with a long key`). 
   The celix string hash map performance, for finding an entry in the hash map, is quite close to the std::unordered_map with a string key. 
   
   



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390458663


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -94,11 +83,11 @@ static unsigned int celix_longHashMap_hash(const celix_hash_map_key_t* key) {
     return key->longKey ^ (key->longKey >> (sizeof(key->longKey)*8/2));

Review Comment:
   I think this has been addressed by trying out and selecting a different long hash function. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378679080


##########
libs/utils/gtest/CMakeLists.txt:
##########
@@ -43,7 +43,7 @@ celix_deprecated_utils_headers(test_utils)
 configure_file(resources/properties.txt ${CMAKE_CURRENT_BINARY_DIR}/resources-test/properties.txt COPYONLY)
 
 if (CELIX_CXX17)
-    add_library(test_utils_cxx17tests STATIC
+    add_library(test_utils_cxx17tests OBJECT

Review Comment:
   ~~I have not figured out why static library does not work.~~
   All unresolved symbols of the executable `test_utils` are satisfied by other library, and it does not use any symbols in `test_utils_cxx17tests`. Therefore, `test_utils_cxx17tests` is totally ignored.
   
   Changing `test_utils_cxx17tests` to object library essentially makes it part of `test_utils`.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1378791024


##########
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) {
+            rc = fputs(header, file);
         }
-
-        if (line == NULL){
-            break;
+        if (rc != 0) {
+            rc = fputc('\n', file);
         }
+    }
 
-        parseLine(line, props);
-    } while(line != NULL);
+    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;
+}
 
-    free(in);
+celix_properties_t* celix_properties_copy(const celix_properties_t* properties) {
+    celix_properties_t* copy = celix_properties_create();
+    if (properties == NULL) {
+        return copy;
+    }
 
-    return props;
+    CELIX_PROPERTIES_ITERATE(properties, iter) {
+        if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_STRING) {
+            celix_properties_set(copy, iter.key, iter.entry.value);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_LONG) {
+            celix_properties_setLong(copy, iter.key, iter.entry.typed.longValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_DOUBLE) {
+            celix_properties_setDouble(copy, iter.key, iter.entry.typed.doubleValue);
+        } else if (iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_BOOL) {
+            celix_properties_setBool(copy, iter.key, iter.entry.typed.boolValue);
+        } else /*version*/ {
+            assert(iter.entry.valueType == CELIX_PROPERTIES_VALUE_TYPE_VERSION);
+            celix_properties_setVersion(copy, iter.key, iter.entry.typed.versionValue);
+        }
+    }
+    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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                fputc('\n', file);
+celix_status_t celix_properties_setWithoutCopy(celix_properties_t* properties, char* key, char* value) {
+    if (properties != NULL && key != NULL && value != NULL) {
+        celix_properties_entry_t* entry = celix_properties_createEntryWithNoCopy(properties, value);
+        if (!entry) {
+            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);

Review Comment:
   Memory leak when error.



-- 
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-unsubscribe@celix.apache.org

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


[GitHub] [celix] pnoltes commented on pull request #470: [RFC] type support for properties

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1501149664

   Note because this will be merged when Celix 3.0.0 is prepared also:
   
   - [ ] Update the put functions for celix string/long hash map with a celix_status_t return so that the return value can be used to indicate a CELIX_ENOMEN error. Note that currently the previous stored value is returned.
   - [ ] Update the celix properties set functions with a celix_status_t return so that the return value can be used to indicate a CELIX_ENOMEM error
   - [ ] Add celix ei functions for celix string/long hash map put functions and celix properties set functions


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] [RFC] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1570648807

   > How about we provide a unified way of serialization (e.g. netstring) so that it can be used for both file storage and EventAdmin?
   
   I agree and I will also look into this when we start working on Celix 3.0.0. I also think we should (de)serialize with type information.
   
   


-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390182014


##########
libs/utils/src/properties.c:
##########
@@ -191,324 +481,449 @@ 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+");
 
-    char *in = strdup(input);
-    char *line = NULL;
-    char *saveLinePointer = NULL;
+    if (file == NULL) {
+        celix_err_pushf("Cannot open file '%s'", filename);
+        return CELIX_FILE_IO_EXCEPTION;
+    }
+
+    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) {
+            rc = fputs(header, file);
+        }
+        if (rc != 0) {
+            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 celix_steal_ptr(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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                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);
         }
-        fclose(file);
-    } else {
-        perror("File is null");
+        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->value);

Review Comment:
   This seems unnatural. Our user is not instructed how to construct an entry, and he/she may not know that `entry->typed.strValue` is ignored.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
PengZheng commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1391120936


##########
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));

Review Comment:
   Maybe we delay it to when solving #685.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389455788


##########
libs/utils/include/celix_string_hash_map.h:
##########
@@ -90,14 +94,37 @@ typedef struct celix_string_hash_map_create_options {
      * only the simpledRemoveCallback will be used.
      *
      * Default is NULL.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] removedKey The key of the value that was removed from the hash map.
+     *                       Note that the removedKey can still be in use if the a value entry for the same key is
+     *                       replaced, so this callback should not free the removedKey.
+     * @param[in] removedValue The value that was removed from the hash map. This value is no longer used by the
+     *                         hash map and can be freed.
      */
     void (*removedCallback)(void* data, const char* removedKey, celix_hash_map_value_t removedValue) CELIX_OPTS_INIT;
 
+    /**
+     * @brief A removed key callback, which if provided will be called if a key is no longer used in the hash map.
+     *
+     * @param[in] data The void pointer to the data that was provided when the callback was set as removedCallbackData.
+     * @param[in] key The key that is no longer used in the hash map. if `storeKeysWeakly` was configured as true,
+     *                the key can be freed.
+     */
+    void (*removedKeyCallback)(void* data, char* key) CELIX_OPTS_INIT;
+
     /**
      * @brief If set to true, the string hash map will not make of copy of the keys and assumes
      * that the keys are in scope/memory for the complete lifecycle of the string hash map.
      *
-     * Note that this changes the default behaviour of the celix_stringHashMap_put* functions.
+     * When keys are stored weakly it is the caller responsibility to check the return value of
+     * celix_stringHashMap_put* function calls.
+     * If a celix_stringHashMap_put* function call returns true, the key is used in the hash map and the key
+     * should never be freed or freed in a configured removedKeyCallback.
+     * If a celix_stringHashMap_put* function call returns false, a value is replaced and the already existing
+     * key is reused. If the needed the caller should free the provided key.

Review Comment:
   I updated the documentation to clarify that the users should first check if a key exists with `celix_stringHashMap_hasKey`. This will lead to some more code, but gives users the flexibility - if possible - to only allocate a key if needed (as is done in properties).
   
   It is also possible to call the removeKeyCallback for every not used key, but I think this does not really work with the short properties string optimization buffer.
   
   An other option could be to add a set of  `celix_stringHashMap_replace*` functions, with a boolean output argument. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389228884


##########
libs/utils/gtest/src/CxxPropertiesTestSuite.cc:
##########
@@ -52,14 +52,37 @@ TEST_F(CxxPropertiesTestSuite, testFillAndLoop) {
     EXPECT_EQ(props.getAsBool("key5", false), true);
 
     int count = 0;
+    for (auto it = props.begin(); it != props.end(); ++it) {

Review Comment:
   The following range based test used a const ref, so uses `cbegin()` and `cend()`. This way we test 2 styles and both the const and mutable variant.



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1390437669


##########
libs/utils/src/celix_hash_map.c:
##########
@@ -599,33 +675,49 @@ bool celix_longHashMapIterator_isEnd(const celix_long_hash_map_iterator_t* iter)
 void celix_stringHashMapIterator_next(celix_string_hash_map_iterator_t* iter) {
     const celix_hash_map_t* map = iter->_internal[0];
     celix_hash_map_entry_t *entry = iter->_internal[1];
+    iter->index += 1;

Review Comment:
   I designed the hashmap/properties iterator based on C++ iterator with begin() and end(). For C++ the `end()` iterator is past the last element. So IMO it is correct that the index of the end iterator is the same as the size of the map. 



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -256,57 +255,46 @@ static bool celix_hashMap_putValue(celix_hash_map_t* map, const char* strKey, lo
             if (replacedValueOut != NULL) {
                 *replacedValueOut = entry->value;
             }
+            celix_hashMap_callRemovedCallback(map, entry);
             memcpy(&entry->value, value, sizeof(*value));
-            return true;
+            return CELIX_SUCCESS;
         }
     }
-    celix_hashMap_addEntry(map, hash, &key, value, index);
-    if (replacedValueOut != NULL) {
+    celix_status_t status = celix_hashMap_addEntry(map, hash, &key, value, index);
+    if (status == CELIX_SUCCESS && replacedValueOut != NULL) {
         memset(replacedValueOut, 0, sizeof(*replacedValueOut));
     }
-    return false;
+    return status;
 }
 
-static void* celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v) {
+static celix_status_t celix_hashMap_put(celix_hash_map_t* map, const char* strKey, long longKey, void* v, void** previousValueOut) {

Review Comment:
   removed



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1389464529


##########
libs/utils/include/celix_properties.h:
##########
@@ -17,86 +17,474 @@
  * under the License.
  */
 
-#ifndef CELIX_PROPERTIES_H_
-#define CELIX_PROPERTIES_H_
+/**
+ * @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*
+ */
 
 #include <stdio.h>
-#include <stdbool.h>
 
 #include "celix_cleanup.h"
 #include "celix_compiler.h"
 #include "celix_errno.h"
 #include "celix_utils_export.h"
+#include "celix_version.h"
+
+#ifndef CELIX_PROPERTIES_H_
+#define CELIX_PROPERTIES_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 index of the current iterator.
+     */
+    size_t index;
 
+    /**
+     * @brief Te current key.
+     */
+    const char* key;
 
-/**********************************************************************************************************************
- **********************************************************************************************************************
- * Updated API
- **********************************************************************************************************************
- **********************************************************************************************************************/
+    /**
+     * @brief The current value entry.
+     */
+    celix_properties_entry_t entry;
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create(void);
+    /**
+     * @brief Private data used to implement the iterator.
+     */
+    char _data[56];
+} celix_properties_iterator_t;
 
-CELIX_UTILS_EXPORT void celix_properties_destroy(celix_properties_t *properties);
+/**
+ * @brief Create a new empty property set.
+ *
+ * @return A new empty property set.
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_create();
+
+/**
+ * @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.
+ *
+ * @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);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE *stream);
+/**
+ * @brief Load properties from a stream.
+ *
+ * @param[in,out] stream The stream to load properties from.
+ * @return A property set containing the properties from the stream.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadWithStream(FILE* stream);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char *input);
+/**
+ * @brief Load properties from a string.
+ *
+ * @param[in] input The string to load properties from.
+ * @return A property set containing the properties from the string.
+ * @retval NULL If an error occurred (e.g. invalid format).
+ */
+CELIX_UTILS_EXPORT celix_properties_t* celix_properties_loadFromString(const char* input);
 
-CELIX_UTILS_EXPORT void celix_properties_store(celix_properties_t *properties, const char *file, const char *header);
+/**
+ * @brief Store properties to a file.
+ *
+ * @note Properties values are always stored as string values, regardless of their actual underlining types.
+ *
+ * @param[in] properties The property set to store.
+ * @param[in] file The name of the file to store the properties to.
+ * @param[in] header An optional - 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);
 
-CELIX_UTILS_EXPORT const char* celix_properties_get(const celix_properties_t *properties, const char *key, const char *defaultValue);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_set(celix_properties_t *properties, const char *key, const char *value);
+/**
+ * @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);
 
-CELIX_UTILS_EXPORT void celix_properties_setWithoutCopy(celix_properties_t *properties, char *key, char *value);
+/**
+ * @brief Get the type of a property value.
+ *
+ * @param[in] properties The property set to search.
+ * @param[in] key The key of the property to get the type of.
+ * @return The type of the property value, or CELIX_PROPERTIES_VALUE_TYPE_UNSET if the property is not set.
+ */
+CELIX_UTILS_EXPORT celix_properties_value_type_e celix_properties_getType(const celix_properties_t* properties,
+                                                                          const char* key);
 
-CELIX_UTILS_EXPORT void celix_properties_unset(celix_properties_t *properties, const char *key);
+/**
+ * @brief Set the value of a property.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set.
+ * @param[in] value The value to set the property to.
+ * @return CELIX_SUCCESS if the operation was successful, CELIX_ENOMEM if there was not enough memory to set the entry.
+ */
+CELIX_UTILS_EXPORT celix_status_t celix_properties_set(celix_properties_t* properties,
+                                                       const char* key,
+                                                       const char* value);
 
-CELIX_UTILS_EXPORT celix_properties_t* celix_properties_copy(const celix_properties_t *properties);
+/**
+ * @brief Set the value of a property without copying the value string.
+ *
+ * @param[in] properties The property set to modify.
+ * @param[in] key The key of the property to set. This string will be used directly, so it must not be freed or

Review Comment:
   updated the documentation to clarify this



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1377967375


##########
bundles/pubsub/pubsub_protocol/pubsub_protocol_lib/src/pubsub_wire_protocol_common.c:
##########
@@ -101,19 +101,17 @@ celix_status_t pubsubProtocol_encodeMetadata(pubsub_protocol_message_t* message,
             *bufferInOut = newBuffer;
             *bufferLengthInOut = newLength;
         }
-        const char* key;
         if (metadataSize == 0) {
             encoded = true;
             continue;
         }
         celix_status_t status = CELIX_SUCCESS;
-        CELIX_PROPERTIES_FOR_EACH(message->metadata.metadata, key) {
-            const char *val = celix_properties_get(message->metadata.metadata, key, "");
+        CELIX_PROPERTIES_ITERATE(message->metadata.metadata, iter) {
             if (status == CELIX_SUCCESS) {
-                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, key);
+                status = pubsubProtocol_addNetstringEntryToBuffer(*bufferInOut, *bufferLengthInOut, &offset, iter.key);

Review Comment:
   A value cannot be NULL (see previous comment) and a NULL value as key is also not accepted:
   ```
   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
       }
   ```



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1399582716


##########
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);

Review Comment:
   added ftell return check and error injection test



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1398214396


##########
libs/utils/src/properties.c:
##########
@@ -191,324 +481,449 @@ 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+");
 
-    char *in = strdup(input);
-    char *line = NULL;
-    char *saveLinePointer = NULL;
+    if (file == NULL) {
+        celix_err_pushf("Cannot open file '%s'", filename);
+        return CELIX_FILE_IO_EXCEPTION;
+    }
+
+    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) {
+            rc = fputs(header, file);
+        }
+        if (rc != 0) {
+            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 celix_steal_ptr(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) {
+    return celix_properties_createAndSetEntry(properties, key, &value, NULL, NULL, NULL, NULL);
+}
 
-                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);
         }
-        fclose(file);
-    } else {
-        perror("File is null");
+        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->value);

Review Comment:
   I agree, updated this to entry->typed.strValue and updated the code documentation to indicate what is used in the entry for copying. 



##########
libs/utils/src/celix_hash_map.c:
##########
@@ -19,64 +19,56 @@
 
 #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 <stdlib.h>
 #include <memory.h>
 #include <math.h>
 #include <assert.h>
 #include <stdint.h>
 
-/**
- * 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;
     celix_hash_map_entry_t* next;
     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 modificationsCount;
+    size_t resizeCount;
+    size_t collisionsCount;

Review Comment:
   removed



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "pnoltes (via GitHub)" <gi...@apache.org>.
pnoltes commented on code in PR #470:
URL: https://github.com/apache/celix/pull/470#discussion_r1397777996


##########
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:
   updated to 8. 



-- 
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-unsubscribe@celix.apache.org

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


Re: [PR] type support for properties (celix)

Posted by "PengZheng (via GitHub)" <gi...@apache.org>.
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-unsubscribe@celix.apache.org

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


[GitHub] [celix] codecov-commenter commented on pull request #470: [RFC] type support for properties

Posted by GitBox <gi...@apache.org>.
codecov-commenter commented on PR #470:
URL: https://github.com/apache/celix/pull/470#issuecomment-1380157483

   # [Codecov](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation) Report
   > Merging [#470](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation) (fe434a0) into [master](https://codecov.io/gh/apache/celix/commit/2ccce27a7cfd6d1dec3ab1537271dc31d574d8ec?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation) (2ccce27) will **increase** coverage by `0.07%`.
   > The diff coverage is `90.82%`.
   
   ```diff
   @@            Coverage Diff             @@
   ##           master     #470      +/-   ##
   ==========================================
   + Coverage   74.29%   74.36%   +0.07%     
   ==========================================
     Files         199      215      +16     
     Lines       29600    32821    +3221     
   ==========================================
   + Hits        21990    24408    +2418     
   - Misses       7610     8413     +803     
   ```
   
   
   | [Impacted Files](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation) | Coverage Δ | |
   |---|---|---|
   | [...ubsub/pubsub\_admin\_udp\_mc/src/pubsub\_udpmc\_admin.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-YnVuZGxlcy9wdWJzdWIvcHVic3ViX2FkbWluX3VkcF9tYy9zcmMvcHVic3ViX3VkcG1jX2FkbWluLmM=) | `41.73% <0.00%> (+0.10%)` | :arrow_up: |
   | [...ubsub\_admin\_websocket/src/pubsub\_websocket\_admin.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-YnVuZGxlcy9wdWJzdWIvcHVic3ViX2FkbWluX3dlYnNvY2tldC9zcmMvcHVic3ViX3dlYnNvY2tldF9hZG1pbi5j) | `42.76% <0.00%> (+0.28%)` | :arrow_up: |
   | [libs/framework/include/celix/Exception.h](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy9mcmFtZXdvcmsvaW5jbHVkZS9jZWxpeC9FeGNlcHRpb24uaA==) | `0.00% <0.00%> (ø)` | |
   | [libs/framework/src/service\_reference.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy9mcmFtZXdvcmsvc3JjL3NlcnZpY2VfcmVmZXJlbmNlLmM=) | `57.98% <0.00%> (+1.01%)` | :arrow_up: |
   | [libs/framework/src/celix\_launcher.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy9mcmFtZXdvcmsvc3JjL2NlbGl4X2xhdW5jaGVyLmM=) | `21.25% <15.38%> (-0.12%)` | :arrow_down: |
   | [...e\_service\_admin\_dfi/src/remote\_service\_admin\_dfi.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-YnVuZGxlcy9yZW1vdGVfc2VydmljZXMvcmVtb3RlX3NlcnZpY2VfYWRtaW5fZGZpL3NyYy9yZW1vdGVfc2VydmljZV9hZG1pbl9kZmkuYw==) | `85.02% <16.66%> (+0.68%)` | :arrow_up: |
   | [libs/utils/src/utils.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy91dGlscy9zcmMvdXRpbHMuYw==) | `89.47% <88.88%> (+0.16%)` | :arrow_up: |
   | [libs/utils/src/properties.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy91dGlscy9zcmMvcHJvcGVydGllcy5j) | `94.71% <93.63%> (+13.81%)` | :arrow_up: |
   | [libs/utils/include/celix/Properties.h](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-bGlicy91dGlscy9pbmNsdWRlL2NlbGl4L1Byb3BlcnRpZXMuaA==) | `95.80% <94.56%> (-1.54%)` | :arrow_down: |
   | [bundles/pubsub/pubsub\_spi/src/pubsub\_endpoint.c](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation#diff-YnVuZGxlcy9wdWJzdWIvcHVic3ViX3NwaS9zcmMvcHVic3ViX2VuZHBvaW50LmM=) | `71.84% <100.00%> (-0.28%)` | :arrow_down: |
   | ... and [29 more](https://codecov.io/gh/apache/celix/pull/470?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation) | |
   
   :mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=The+Apache+Software+Foundation)
   


-- 
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-unsubscribe@celix.apache.org

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