DPF

DISTRHO Plugin Framework
Log | Files | Refs | Submodules | README | LICENSE

String.hpp (25691B)


      1 /*
      2  * DISTRHO Plugin Framework (DPF)
      3  * Copyright (C) 2012-2024 Filipe Coelho <falktx@falktx.com>
      4  *
      5  * Permission to use, copy, modify, and/or distribute this software for any purpose with
      6  * or without fee is hereby granted, provided that the above copyright notice and this
      7  * permission notice appear in all copies.
      8  *
      9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
     10  * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
     11  * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
     12  * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
     13  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
     14  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #ifndef DISTRHO_STRING_HPP_INCLUDED
     18 #define DISTRHO_STRING_HPP_INCLUDED
     19 
     20 #include "../DistrhoUtils.hpp"
     21 #include "../extra/ScopedSafeLocale.hpp"
     22 
     23 #include <algorithm>
     24 
     25 START_NAMESPACE_DISTRHO
     26 
     27 // -----------------------------------------------------------------------
     28 // String class
     29 
     30 class String
     31 {
     32 public:
     33     // -------------------------------------------------------------------
     34     // constructors (no explicit conversions allowed)
     35 
     36     /*
     37      * Empty string.
     38      */
     39     explicit String() noexcept
     40         : fBuffer(_null()),
     41           fBufferLen(0),
     42           fBufferAlloc(false) {}
     43 
     44     /*
     45      * Simple character.
     46      */
     47     explicit String(const char c) noexcept
     48         : fBuffer(_null()),
     49           fBufferLen(0),
     50           fBufferAlloc(false)
     51     {
     52         char ch[2];
     53         ch[0] = c;
     54         ch[1] = '\0';
     55 
     56         _dup(ch);
     57     }
     58 
     59     /*
     60      * Simple char string.
     61      */
     62     explicit String(char* const strBuf, const bool reallocData = true) noexcept
     63         : fBuffer(_null()),
     64           fBufferLen(0),
     65           fBufferAlloc(false)
     66     {
     67         if (reallocData || strBuf == nullptr)
     68         {
     69             _dup(strBuf);
     70         }
     71         else
     72         {
     73             fBuffer      = strBuf;
     74             fBufferLen   = std::strlen(strBuf);
     75             fBufferAlloc = true;
     76         }
     77     }
     78 
     79     /*
     80      * Simple const char string.
     81      */
     82     explicit String(const char* const strBuf) noexcept
     83         : fBuffer(_null()),
     84           fBufferLen(0),
     85           fBufferAlloc(false)
     86     {
     87         _dup(strBuf);
     88     }
     89 
     90     /*
     91      * Integer.
     92      */
     93     explicit String(const int value) noexcept
     94         : fBuffer(_null()),
     95           fBufferLen(0),
     96           fBufferAlloc(false)
     97     {
     98         char strBuf[0xff+1];
     99         std::snprintf(strBuf, 0xff, "%d", value);
    100         strBuf[0xff] = '\0';
    101 
    102         _dup(strBuf);
    103     }
    104 
    105     /*
    106      * Unsigned integer, possibly in hexadecimal.
    107      */
    108     explicit String(const unsigned int value, const bool hexadecimal = false) noexcept
    109         : fBuffer(_null()),
    110           fBufferLen(0),
    111           fBufferAlloc(false)
    112     {
    113         char strBuf[0xff+1];
    114         std::snprintf(strBuf, 0xff, hexadecimal ? "0x%x" : "%u", value);
    115         strBuf[0xff] = '\0';
    116 
    117         _dup(strBuf);
    118     }
    119 
    120     /*
    121      * Long integer.
    122      */
    123     explicit String(const long value) noexcept
    124         : fBuffer(_null()),
    125           fBufferLen(0),
    126           fBufferAlloc(false)
    127     {
    128         char strBuf[0xff+1];
    129         std::snprintf(strBuf, 0xff, "%ld", value);
    130         strBuf[0xff] = '\0';
    131 
    132         _dup(strBuf);
    133     }
    134 
    135     /*
    136      * Long unsigned integer, possibly hexadecimal.
    137      */
    138     explicit String(const unsigned long value, const bool hexadecimal = false) noexcept
    139         : fBuffer(_null()),
    140           fBufferLen(0),
    141           fBufferAlloc(false)
    142     {
    143         char strBuf[0xff+1];
    144         std::snprintf(strBuf, 0xff, hexadecimal ? "0x%lx" : "%lu", value);
    145         strBuf[0xff] = '\0';
    146 
    147         _dup(strBuf);
    148     }
    149 
    150     /*
    151      * Long long integer.
    152      */
    153     explicit String(const long long value) noexcept
    154         : fBuffer(_null()),
    155           fBufferLen(0),
    156           fBufferAlloc(false)
    157     {
    158         char strBuf[0xff+1];
    159         std::snprintf(strBuf, 0xff, "%lld", value);
    160         strBuf[0xff] = '\0';
    161 
    162         _dup(strBuf);
    163     }
    164 
    165     /*
    166      * Long long unsigned integer, possibly hexadecimal.
    167      */
    168     explicit String(const unsigned long long value, const bool hexadecimal = false) noexcept
    169         : fBuffer(_null()),
    170           fBufferLen(0),
    171           fBufferAlloc(false)
    172     {
    173         char strBuf[0xff+1];
    174         std::snprintf(strBuf, 0xff, hexadecimal ? "0x%llx" : "%llu", value);
    175         strBuf[0xff] = '\0';
    176 
    177         _dup(strBuf);
    178     }
    179 
    180     /*
    181      * Single-precision floating point number.
    182      */
    183     explicit String(const float value) noexcept
    184         : fBuffer(_null()),
    185           fBufferLen(0),
    186           fBufferAlloc(false)
    187     {
    188         char strBuf[0xff+1];
    189 
    190         {
    191             const ScopedSafeLocale ssl;
    192             std::snprintf(strBuf, 0xff, "%.12g", static_cast<double>(value));
    193         }
    194 
    195         strBuf[0xff] = '\0';
    196 
    197         _dup(strBuf);
    198     }
    199 
    200     /*
    201      * Double-precision floating point number.
    202      */
    203     explicit String(const double value) noexcept
    204         : fBuffer(_null()),
    205           fBufferLen(0),
    206           fBufferAlloc(false)
    207     {
    208         char strBuf[0xff+1];
    209 
    210         {
    211             const ScopedSafeLocale ssl;
    212             std::snprintf(strBuf, 0xff, "%.24g", value);
    213         }
    214 
    215         strBuf[0xff] = '\0';
    216 
    217         _dup(strBuf);
    218     }
    219 
    220     // -------------------------------------------------------------------
    221     // non-explicit constructor
    222 
    223     /*
    224      * Create string from another string.
    225      */
    226     String(const String& str) noexcept
    227         : fBuffer(_null()),
    228           fBufferLen(0),
    229           fBufferAlloc(false)
    230     {
    231         _dup(str.fBuffer);
    232     }
    233 
    234     // -------------------------------------------------------------------
    235     // destructor
    236 
    237     /*
    238      * Destructor.
    239      */
    240     ~String() noexcept
    241     {
    242         DISTRHO_SAFE_ASSERT_RETURN(fBuffer != nullptr,);
    243 
    244         if (fBufferAlloc)
    245             std::free(fBuffer);
    246 
    247         fBuffer      = nullptr;
    248         fBufferLen   = 0;
    249         fBufferAlloc = false;
    250     }
    251 
    252     // -------------------------------------------------------------------
    253     // public methods
    254 
    255     /*
    256      * Get length of the string.
    257      */
    258     std::size_t length() const noexcept
    259     {
    260         return fBufferLen;
    261     }
    262 
    263     /*
    264      * Check if the string is empty.
    265      */
    266     bool isEmpty() const noexcept
    267     {
    268         return (fBufferLen == 0);
    269     }
    270 
    271     /*
    272      * Check if the string is not empty.
    273      */
    274     bool isNotEmpty() const noexcept
    275     {
    276         return (fBufferLen != 0);
    277     }
    278 
    279     /*
    280      * Check if the string contains a specific character, case-sensitive.
    281      */
    282     bool contains(const char c) const noexcept
    283     {
    284         for (std::size_t i=0; i<fBufferLen; ++i)
    285         {
    286             if (fBuffer[i] == c)
    287                 return true;
    288         }
    289 
    290         return false;
    291     }
    292 
    293     /*
    294      * Check if the string contains another string, optionally ignoring case.
    295      */
    296     bool contains(const char* const strBuf, const bool ignoreCase = false) const noexcept
    297     {
    298         DISTRHO_SAFE_ASSERT_RETURN(strBuf != nullptr, false);
    299 
    300         if (ignoreCase)
    301         {
    302 #ifdef __USE_GNU
    303             return (strcasestr(fBuffer, strBuf) != nullptr);
    304 #else
    305             String tmp1(fBuffer), tmp2(strBuf);
    306 
    307             // memory allocation failed or empty string(s)
    308             if (tmp1.fBuffer == _null() || tmp2.fBuffer == _null())
    309                 return false;
    310 
    311             tmp1.toLower();
    312             tmp2.toLower();
    313             return (std::strstr(tmp1, tmp2) != nullptr);
    314 #endif
    315         }
    316 
    317         return (std::strstr(fBuffer, strBuf) != nullptr);
    318     }
    319 
    320     /*
    321      * Check if character at 'pos' is a digit.
    322      */
    323     bool isDigit(const std::size_t pos) const noexcept
    324     {
    325         DISTRHO_SAFE_ASSERT_RETURN(pos < fBufferLen, false);
    326 
    327         return (fBuffer[pos] >= '0' && fBuffer[pos] <= '9');
    328     }
    329 
    330     /*
    331      * Check if the string starts with the character 'c'.
    332      */
    333     bool startsWith(const char c) const noexcept
    334     {
    335         DISTRHO_SAFE_ASSERT_RETURN(c != '\0', false);
    336 
    337         return (fBufferLen > 0 && fBuffer[0] == c);
    338     }
    339 
    340     /*
    341      * Check if the string starts with the string 'prefix'.
    342      */
    343     bool startsWith(const char* const prefix) const noexcept
    344     {
    345         DISTRHO_SAFE_ASSERT_RETURN(prefix != nullptr, false);
    346 
    347         const std::size_t prefixLen(std::strlen(prefix));
    348 
    349         if (fBufferLen < prefixLen)
    350             return false;
    351 
    352         return (std::strncmp(fBuffer, prefix, prefixLen) == 0);
    353     }
    354 
    355     /*
    356      * Check if the string ends with the character 'c'.
    357      */
    358     bool endsWith(const char c) const noexcept
    359     {
    360         DISTRHO_SAFE_ASSERT_RETURN(c != '\0', false);
    361 
    362         return (fBufferLen > 0 && fBuffer[fBufferLen-1] == c);
    363     }
    364 
    365     /*
    366      * Check if the string ends with the string 'suffix'.
    367      */
    368     bool endsWith(const char* const suffix) const noexcept
    369     {
    370         DISTRHO_SAFE_ASSERT_RETURN(suffix != nullptr, false);
    371 
    372         const std::size_t suffixLen(std::strlen(suffix));
    373 
    374         if (fBufferLen < suffixLen)
    375             return false;
    376 
    377         return (std::strncmp(fBuffer + (fBufferLen-suffixLen), suffix, suffixLen) == 0);
    378     }
    379 
    380     /*
    381      * Find the first occurrence of character 'c' in the string.
    382      * Returns "length()" if the character is not found.
    383      */
    384     std::size_t find(const char c, bool* const found = nullptr) const noexcept
    385     {
    386         if (fBufferLen == 0 || c == '\0')
    387         {
    388             if (found != nullptr)
    389                 *found = false;
    390             return fBufferLen;
    391         }
    392 
    393         for (std::size_t i=0; i < fBufferLen; ++i)
    394         {
    395             if (fBuffer[i] == c)
    396             {
    397                 if (found != nullptr)
    398                     *found = true;
    399                 return i;
    400             }
    401         }
    402 
    403         if (found != nullptr)
    404             *found = false;
    405         return fBufferLen;
    406     }
    407 
    408     /*
    409      * Find the first occurrence of string 'strBuf' in the string.
    410      * Returns "length()" if the string is not found.
    411      */
    412     std::size_t find(const char* const strBuf, bool* const found = nullptr) const noexcept
    413     {
    414         if (fBufferLen == 0 || strBuf == nullptr || strBuf[0] == '\0')
    415         {
    416             if (found != nullptr)
    417                 *found = false;
    418             return fBufferLen;
    419         }
    420 
    421         if (char* const subStrBuf = std::strstr(fBuffer, strBuf))
    422         {
    423             const ssize_t ret(subStrBuf - fBuffer);
    424 
    425             if (ret < 0)
    426             {
    427                 // should never happen!
    428                 d_safe_assert_int("ret >= 0", __FILE__, __LINE__, int(ret));
    429 
    430                 if (found != nullptr)
    431                     *found = false;
    432                 return fBufferLen;
    433             }
    434 
    435             if (found != nullptr)
    436                 *found = true;
    437             return static_cast<std::size_t>(ret);
    438         }
    439 
    440         if (found != nullptr)
    441             *found = false;
    442         return fBufferLen;
    443     }
    444 
    445     /*
    446      * Find the last occurrence of character 'c' in the string.
    447      * Returns "length()" if the character is not found.
    448      */
    449     std::size_t rfind(const char c, bool* const found = nullptr) const noexcept
    450     {
    451         if (fBufferLen == 0 || c == '\0')
    452         {
    453             if (found != nullptr)
    454                 *found = false;
    455             return fBufferLen;
    456         }
    457 
    458         for (std::size_t i=fBufferLen; i > 0; --i)
    459         {
    460             if (fBuffer[i-1] == c)
    461             {
    462                 if (found != nullptr)
    463                     *found = true;
    464                 return i-1;
    465             }
    466         }
    467 
    468         if (found != nullptr)
    469             *found = false;
    470         return fBufferLen;
    471     }
    472 
    473     /*
    474      * Find the last occurrence of string 'strBuf' in the string.
    475      * Returns "length()" if the string is not found.
    476      */
    477     std::size_t rfind(const char* const strBuf, bool* const found = nullptr) const noexcept
    478     {
    479         if (found != nullptr)
    480             *found = false;
    481 
    482         if (fBufferLen == 0 || strBuf == nullptr || strBuf[0] == '\0')
    483             return fBufferLen;
    484 
    485         const std::size_t strBufLen(std::strlen(strBuf));
    486 
    487         std::size_t ret = fBufferLen;
    488         const char* tmpBuf = fBuffer;
    489 
    490         for (std::size_t i=0; i < fBufferLen; ++i)
    491         {
    492             if (std::strstr(tmpBuf+1, strBuf) == nullptr && std::strncmp(tmpBuf, strBuf, strBufLen) == 0)
    493             {
    494                 if (found != nullptr)
    495                     *found = true;
    496                 break;
    497             }
    498 
    499             --ret;
    500             ++tmpBuf;
    501         }
    502 
    503         return fBufferLen-ret;
    504     }
    505 
    506     /*
    507      * Clear the string.
    508      */
    509     void clear() noexcept
    510     {
    511         truncate(0);
    512     }
    513 
    514     /*
    515      * Replace all occurrences of character 'before' with character 'after'.
    516      */
    517     String& replace(const char before, const char after) noexcept
    518     {
    519         DISTRHO_SAFE_ASSERT_RETURN(before != '\0' /* && after != '\0' */, *this);
    520 
    521         for (std::size_t i=0; i < fBufferLen; ++i)
    522         {
    523             if (fBuffer[i] == before)
    524                 fBuffer[i] = after;
    525         }
    526 
    527         return *this;
    528     }
    529 
    530     /*
    531      * Remove all occurrences of character 'c', shifting and truncating the string as necessary.
    532      */
    533     String& remove(const char c) noexcept
    534     {
    535         DISTRHO_SAFE_ASSERT_RETURN(c != '\0', *this);
    536 
    537         if (fBufferLen == 0)
    538             return *this;
    539 
    540         for (std::size_t i=0; i < fBufferLen; ++i)
    541         {
    542             if (fBuffer[i] == c)
    543             {
    544                 --fBufferLen;
    545                 std::memmove(fBuffer+i, fBuffer+i+1, fBufferLen-i);
    546             }
    547         }
    548 
    549         fBuffer[fBufferLen] = '\0';
    550         return *this;
    551     }
    552 
    553     /*
    554      * Truncate the string to size 'n'.
    555      */
    556     String& truncate(const std::size_t n) noexcept
    557     {
    558         if (n >= fBufferLen)
    559             return *this;
    560 
    561         fBuffer[n] = '\0';
    562         fBufferLen = n;
    563 
    564         return *this;
    565     }
    566 
    567     /*
    568      * Convert all non-basic characters to '_'.
    569      */
    570     String& toBasic() noexcept
    571     {
    572         for (std::size_t i=0; i < fBufferLen; ++i)
    573         {
    574             if (fBuffer[i] >= '0' && fBuffer[i] <= '9')
    575                 continue;
    576             if (fBuffer[i] >= 'A' && fBuffer[i] <= 'Z')
    577                 continue;
    578             if (fBuffer[i] >= 'a' && fBuffer[i] <= 'z')
    579                 continue;
    580             if (fBuffer[i] == '_')
    581                 continue;
    582 
    583             fBuffer[i] = '_';
    584         }
    585 
    586         return *this;
    587     }
    588 
    589     /*
    590      * Convert all ascii characters to lowercase.
    591      */
    592     String& toLower() noexcept
    593     {
    594         static const char kCharDiff('a' - 'A');
    595 
    596         for (std::size_t i=0; i < fBufferLen; ++i)
    597         {
    598             if (fBuffer[i] >= 'A' && fBuffer[i] <= 'Z')
    599                 fBuffer[i] = static_cast<char>(fBuffer[i] + kCharDiff);
    600         }
    601 
    602         return *this;
    603     }
    604 
    605     /*
    606      * Convert all ascii characters to uppercase.
    607      */
    608     String& toUpper() noexcept
    609     {
    610         static const char kCharDiff('a' - 'A');
    611 
    612         for (std::size_t i=0; i < fBufferLen; ++i)
    613         {
    614             if (fBuffer[i] >= 'a' && fBuffer[i] <= 'z')
    615                 fBuffer[i] = static_cast<char>(fBuffer[i] - kCharDiff);
    616         }
    617 
    618         return *this;
    619     }
    620 
    621     /*
    622      * Create a new string where all non-basic characters are converted to '_'.
    623      * @see toBasic()
    624      */
    625     String asBasic() const noexcept
    626     {
    627         String s(*this);
    628         return s.toBasic();
    629     }
    630 
    631     /*
    632      * Create a new string where all ascii characters are converted lowercase.
    633      * @see toLower()
    634      */
    635     String asLower() const noexcept
    636     {
    637         String s(*this);
    638         return s.toLower();
    639     }
    640 
    641     /*
    642      * Create a new string where all ascii characters are converted to uppercase.
    643      * @see toUpper()
    644      */
    645     String asUpper() const noexcept
    646     {
    647         String s(*this);
    648         return s.toUpper();
    649     }
    650 
    651     /*
    652      * Direct access to the string buffer (read-only).
    653      */
    654     const char* buffer() const noexcept
    655     {
    656         return fBuffer;
    657     }
    658 
    659     /*
    660      * Get and release the string buffer, while also clearing this string.
    661      * This allows to keep a pointer to the buffer after this object is deleted.
    662      * Result must be freed.
    663      */
    664     char* getAndReleaseBuffer() noexcept
    665     {
    666         char* ret = fBufferLen > 0 ? fBuffer : nullptr;
    667         fBuffer = _null();
    668         fBufferLen = 0;
    669         fBufferAlloc = false;
    670         return ret;
    671     }
    672 
    673     // -------------------------------------------------------------------
    674     // base64 stuff, based on http://www.adp-gmbh.ch/cpp/common/base64.html
    675     // Copyright (C) 2004-2008 René Nyffenegger
    676 
    677     static String asBase64(const void* const data, const std::size_t dataSize)
    678     {
    679         static const char* const kBase64Chars =
    680             "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    681             "abcdefghijklmnopqrstuvwxyz"
    682             "0123456789+/";
    683 
    684        #ifndef _MSC_VER
    685         const std::size_t kTmpBufSize = std::min(d_nextPowerOf2(static_cast<uint32_t>(dataSize/3)), 65536U);
    686        #else
    687         constexpr std::size_t kTmpBufSize = 65536U;
    688        #endif
    689 
    690         const uchar* bytesToEncode((const uchar*)data);
    691 
    692         uint i=0, j=0;
    693         uint charArray3[3], charArray4[4];
    694 
    695         char strBuf[kTmpBufSize + 1];
    696         strBuf[kTmpBufSize] = '\0';
    697         std::size_t strBufIndex = 0;
    698 
    699         String ret;
    700 
    701         for (std::size_t s=0; s<dataSize; ++s)
    702         {
    703             charArray3[i++] = *(bytesToEncode++);
    704 
    705             if (i == 3)
    706             {
    707                 charArray4[0] =  (charArray3[0] & 0xfc) >> 2;
    708                 charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
    709                 charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
    710                 charArray4[3] =   charArray3[2] & 0x3f;
    711 
    712                 for (i=0; i<4; ++i)
    713                     strBuf[strBufIndex++] = kBase64Chars[charArray4[i]];
    714 
    715                 if (strBufIndex >= kTmpBufSize-7)
    716                 {
    717                     strBuf[strBufIndex] = '\0';
    718                     strBufIndex = 0;
    719                     ret += strBuf;
    720                 }
    721 
    722                 i = 0;
    723             }
    724         }
    725 
    726         if (i != 0)
    727         {
    728             for (j=i; j<3; ++j)
    729               charArray3[j] = '\0';
    730 
    731             charArray4[0] =  (charArray3[0] & 0xfc) >> 2;
    732             charArray4[1] = ((charArray3[0] & 0x03) << 4) + ((charArray3[1] & 0xf0) >> 4);
    733             charArray4[2] = ((charArray3[1] & 0x0f) << 2) + ((charArray3[2] & 0xc0) >> 6);
    734             charArray4[3] =   charArray3[2] & 0x3f;
    735 
    736             for (j=0; j<4 && i<3 && j<i+1; ++j)
    737                 strBuf[strBufIndex++] = kBase64Chars[charArray4[j]];
    738 
    739             for (; i++ < 3;)
    740                 strBuf[strBufIndex++] = '=';
    741         }
    742 
    743         if (strBufIndex != 0)
    744         {
    745             strBuf[strBufIndex] = '\0';
    746             ret += strBuf;
    747         }
    748 
    749         return ret;
    750     }
    751 
    752     // -------------------------------------------------------------------
    753     // public operators
    754 
    755     operator const char*() const noexcept
    756     {
    757         return fBuffer;
    758     }
    759 
    760     char operator[](const std::size_t pos) const noexcept
    761     {
    762         if (pos < fBufferLen)
    763             return fBuffer[pos];
    764 
    765         d_safe_assert("pos < fBufferLen", __FILE__, __LINE__);
    766 
    767         static char fallback;
    768         fallback = '\0';
    769         return fallback;
    770     }
    771 
    772     char& operator[](const std::size_t pos) noexcept
    773     {
    774         if (pos < fBufferLen)
    775             return fBuffer[pos];
    776 
    777         d_safe_assert("pos < fBufferLen", __FILE__, __LINE__);
    778 
    779         static char fallback;
    780         fallback = '\0';
    781         return fallback;
    782     }
    783 
    784     bool operator==(const char* const strBuf) const noexcept
    785     {
    786         return (strBuf != nullptr && std::strcmp(fBuffer, strBuf) == 0);
    787     }
    788 
    789     bool operator==(const String& str) const noexcept
    790     {
    791         return operator==(str.fBuffer);
    792     }
    793 
    794     bool operator!=(const char* const strBuf) const noexcept
    795     {
    796         return !operator==(strBuf);
    797     }
    798 
    799     bool operator!=(const String& str) const noexcept
    800     {
    801         return !operator==(str.fBuffer);
    802     }
    803 
    804     String& operator=(const char* const strBuf) noexcept
    805     {
    806         _dup(strBuf);
    807 
    808         return *this;
    809     }
    810 
    811     String& operator=(const String& str) noexcept
    812     {
    813         _dup(str.fBuffer);
    814 
    815         return *this;
    816     }
    817 
    818     String& operator+=(const char* const strBuf) noexcept
    819     {
    820         if (strBuf == nullptr || strBuf[0] == '\0')
    821             return *this;
    822 
    823         const std::size_t strBufLen = std::strlen(strBuf);
    824 
    825         // for empty strings, we can just take the appended string as our entire data
    826         if (isEmpty())
    827         {
    828             _dup(strBuf, strBufLen);
    829             return *this;
    830         }
    831 
    832         // we have some data ourselves, reallocate to add the new stuff
    833         char* const newBuf = (char*)realloc(fBuffer, fBufferLen + strBufLen + 1);
    834         DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, *this);
    835 
    836         std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1);
    837 
    838         fBuffer = newBuf;
    839         fBufferLen += strBufLen;
    840 
    841         return *this;
    842     }
    843 
    844     String& operator+=(const String& str) noexcept
    845     {
    846         return operator+=(str.fBuffer);
    847     }
    848 
    849     String operator+(const char* const strBuf) noexcept
    850     {
    851         if (strBuf == nullptr || strBuf[0] == '\0')
    852             return *this;
    853         if (isEmpty())
    854             return String(strBuf);
    855 
    856         const std::size_t strBufLen = std::strlen(strBuf);
    857         const std::size_t newBufSize = fBufferLen + strBufLen;
    858         char* const newBuf = (char*)malloc(newBufSize + 1);
    859         DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());
    860 
    861         std::memcpy(newBuf, fBuffer, fBufferLen);
    862         std::memcpy(newBuf + fBufferLen, strBuf, strBufLen + 1);
    863 
    864         return String(newBuf, false);
    865     }
    866 
    867     String operator+(const String& str) noexcept
    868     {
    869         return operator+(str.fBuffer);
    870     }
    871 
    872     // needed for std::map compatibility
    873     bool operator<(const String& str) const noexcept
    874     {
    875         return std::strcmp(fBuffer, str.fBuffer) < 0;
    876     }
    877 
    878     // -------------------------------------------------------------------
    879 
    880 private:
    881     char*       fBuffer;      // the actual string buffer
    882     std::size_t fBufferLen;   // string length
    883     bool        fBufferAlloc; // wherever the buffer is allocated, not using _null()
    884 
    885     /*
    886      * Static null string.
    887      * Prevents allocation for new and/or empty strings.
    888      */
    889     static char* _null() noexcept
    890     {
    891         static char sNull = '\0';
    892         return &sNull;
    893     }
    894 
    895     /*
    896      * Helper function.
    897      * Called whenever the string needs to be allocated.
    898      *
    899      * Notes:
    900      * - Allocates string only if 'strBuf' is not null and new string contents are different
    901      * - If 'strBuf' is null, 'size' must be 0
    902      */
    903     void _dup(const char* const strBuf, const std::size_t size = 0) noexcept
    904     {
    905         if (strBuf != nullptr)
    906         {
    907             // don't recreate string if contents match
    908             if (std::strcmp(fBuffer, strBuf) == 0)
    909                 return;
    910 
    911             if (fBufferAlloc)
    912                 std::free(fBuffer);
    913 
    914             fBufferLen = (size > 0) ? size : std::strlen(strBuf);
    915             fBuffer    = (char*)std::malloc(fBufferLen+1);
    916 
    917             if (fBuffer == nullptr)
    918             {
    919                 fBuffer      = _null();
    920                 fBufferLen   = 0;
    921                 fBufferAlloc = false;
    922                 return;
    923             }
    924 
    925             fBufferAlloc = true;
    926 
    927             std::strcpy(fBuffer, strBuf);
    928             fBuffer[fBufferLen] = '\0';
    929         }
    930         else
    931         {
    932             DISTRHO_SAFE_ASSERT_UINT(size == 0, static_cast<uint>(size));
    933 
    934             // don't recreate null string
    935             if (! fBufferAlloc)
    936                 return;
    937 
    938             DISTRHO_SAFE_ASSERT(fBuffer != nullptr);
    939             std::free(fBuffer);
    940 
    941             fBuffer      = _null();
    942             fBufferLen   = 0;
    943             fBufferAlloc = false;
    944         }
    945     }
    946 
    947     DISTRHO_PREVENT_HEAP_ALLOCATION
    948 };
    949 
    950 // -----------------------------------------------------------------------
    951 
    952 static inline
    953 String operator+(const String& strBefore, const char* const strBufAfter) noexcept
    954 {
    955     if (strBufAfter == nullptr || strBufAfter[0] == '\0')
    956         return strBefore;
    957     if (strBefore.isEmpty())
    958         return String(strBufAfter);
    959 
    960     const std::size_t strBeforeLen = strBefore.length();
    961     const std::size_t strBufAfterLen = std::strlen(strBufAfter);
    962     const std::size_t newBufSize = strBeforeLen + strBufAfterLen;
    963     char* const newBuf = (char*)malloc(newBufSize + 1);
    964     DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());
    965 
    966     std::memcpy(newBuf, strBefore.buffer(), strBeforeLen);
    967     std::memcpy(newBuf + strBeforeLen, strBufAfter, strBufAfterLen + 1);
    968 
    969     return String(newBuf, false);
    970 }
    971 
    972 static inline
    973 String operator+(const char* const strBufBefore, const String& strAfter) noexcept
    974 {
    975     if (strAfter.isEmpty())
    976         return String(strBufBefore);
    977     if (strBufBefore == nullptr || strBufBefore[0] == '\0')
    978         return strAfter;
    979 
    980     const std::size_t strBufBeforeLen = std::strlen(strBufBefore);
    981     const std::size_t strAfterLen = strAfter.length();
    982     const std::size_t newBufSize = strBufBeforeLen + strAfterLen;
    983     char* const newBuf = (char*)malloc(newBufSize + 1);
    984     DISTRHO_SAFE_ASSERT_RETURN(newBuf != nullptr, String());
    985 
    986     std::memcpy(newBuf, strBufBefore, strBufBeforeLen);
    987     std::memcpy(newBuf + strBufBeforeLen, strAfter.buffer(), strAfterLen + 1);
    988 
    989     return String(newBuf, false);
    990 }
    991 
    992 // -----------------------------------------------------------------------
    993 
    994 END_NAMESPACE_DISTRHO
    995 
    996 #endif // DISTRHO_STRING_HPP_INCLUDED