1 /*
2   ==============================================================================
3 
4    This file is part of the Water library.
5    Copyright (c) 2016 ROLI Ltd.
6    Copyright (C) 2017 Filipe Coelho <falktx@falktx.com>
7 
8    Permission is granted to use this software under the terms of the ISC license
9    http://www.isc.org/downloads/software-support-policy/isc-license/
10 
11    Permission to use, copy, modify, and/or distribute this software for any
12    purpose with or without fee is hereby granted, provided that the above
13    copyright notice and this permission notice appear in all copies.
14 
15    THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD
16    TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
17    FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
18    OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
19    USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20    TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21    OF THIS SOFTWARE.
22 
23   ==============================================================================
24 */
25 
26 #include "StringArray.h"
27 
28 namespace water {
29 
StringArray()30 StringArray::StringArray() noexcept
31 {
32 }
33 
StringArray(const StringArray & other)34 StringArray::StringArray (const StringArray& other)
35     : strings (other.strings)
36 {
37 }
38 
39 #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS
StringArray(StringArray && other)40 StringArray::StringArray (StringArray&& other) noexcept
41     : strings (static_cast<Array <String>&&> (other.strings))
42 {
43 }
44 #endif
45 
StringArray(const String & firstValue)46 StringArray::StringArray (const String& firstValue)
47 {
48     strings.add (firstValue);
49 }
50 
StringArray(const String * initialStrings,int numberOfStrings)51 StringArray::StringArray (const String* initialStrings, int numberOfStrings)
52 {
53     strings.addArray (initialStrings, numberOfStrings);
54 }
55 
StringArray(const char * const * initialStrings)56 StringArray::StringArray (const char* const* initialStrings)
57 {
58     strings.addNullTerminatedArray (initialStrings);
59 }
60 
StringArray(const char * const * initialStrings,int numberOfStrings)61 StringArray::StringArray (const char* const* initialStrings, int numberOfStrings)
62 {
63     strings.addArray (initialStrings, numberOfStrings);
64 }
65 
operator =(const StringArray & other)66 StringArray& StringArray::operator= (const StringArray& other)
67 {
68     strings = other.strings;
69     return *this;
70 }
71 
72 #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS
operator =(StringArray && other)73 StringArray& StringArray::operator= (StringArray&& other) noexcept
74 {
75     strings = static_cast<Array<String>&&> (other.strings);
76     return *this;
77 }
78 #endif
79 
~StringArray()80 StringArray::~StringArray()
81 {
82 }
83 
operator ==(const StringArray & other) const84 bool StringArray::operator== (const StringArray& other) const noexcept
85 {
86     return strings == other.strings;
87 }
88 
operator !=(const StringArray & other) const89 bool StringArray::operator!= (const StringArray& other) const noexcept
90 {
91     return ! operator== (other);
92 }
93 
swapWith(StringArray & other)94 void StringArray::swapWith (StringArray& other) noexcept
95 {
96     strings.swapWith (other.strings);
97 }
98 
clear()99 void StringArray::clear()
100 {
101     strings.clear();
102 }
103 
clearQuick()104 void StringArray::clearQuick()
105 {
106     strings.clearQuick();
107 }
108 
operator [](const int index) const109 const String& StringArray::operator[] (const int index) const noexcept
110 {
111     if (isPositiveAndBelow (index, strings.size()))
112         return strings.getReference (index);
113 
114     static String empty;
115     return empty;
116 }
117 
getReference(const int index)118 String& StringArray::getReference (const int index) noexcept
119 {
120     return strings.getReference (index);
121 }
122 
add(const String & newString)123 bool StringArray::add (const String& newString)
124 {
125     return strings.add (newString);
126 }
127 
128 #if WATER_COMPILER_SUPPORTS_MOVE_SEMANTICS
add(String && stringToAdd)129 bool StringArray::add (String&& stringToAdd)
130 {
131     return strings.add (static_cast<String&&> (stringToAdd));
132 }
133 #endif
134 
insert(const int index,const String & newString)135 bool StringArray::insert (const int index, const String& newString)
136 {
137     return strings.insert (index, newString);
138 }
139 
addIfNotAlreadyThere(const String & newString,const bool ignoreCase)140 bool StringArray::addIfNotAlreadyThere (const String& newString, const bool ignoreCase)
141 {
142     if (contains (newString, ignoreCase))
143         return false;
144 
145     return add (newString);
146 }
147 
addArray(const StringArray & otherArray,int startIndex,int numElementsToAdd)148 void StringArray::addArray (const StringArray& otherArray, int startIndex, int numElementsToAdd)
149 {
150     if (startIndex < 0)
151     {
152         wassertfalse;
153         startIndex = 0;
154     }
155 
156     if (numElementsToAdd < 0 || startIndex + numElementsToAdd > otherArray.size())
157         numElementsToAdd = otherArray.size() - startIndex;
158 
159     while (--numElementsToAdd >= 0)
160         strings.add (otherArray.strings.getReference (startIndex++));
161 }
162 
mergeArray(const StringArray & otherArray,const bool ignoreCase)163 void StringArray::mergeArray (const StringArray& otherArray, const bool ignoreCase)
164 {
165     for (int i = 0; i < otherArray.size(); ++i)
166         addIfNotAlreadyThere (otherArray[i], ignoreCase);
167 }
168 
set(const int index,const String & newString)169 void StringArray::set (const int index, const String& newString)
170 {
171     strings.set (index, newString);
172 }
173 
contains(StringRef stringToLookFor,const bool ignoreCase) const174 bool StringArray::contains (StringRef stringToLookFor, const bool ignoreCase) const
175 {
176     return indexOf (stringToLookFor, ignoreCase) >= 0;
177 }
178 
indexOf(StringRef stringToLookFor,const bool ignoreCase,int i) const179 int StringArray::indexOf (StringRef stringToLookFor, const bool ignoreCase, int i) const
180 {
181     if (i < 0)
182         i = 0;
183 
184     const int numElements = size();
185 
186     if (ignoreCase)
187     {
188         for (; i < numElements; ++i)
189             if (strings.getReference(i).equalsIgnoreCase (stringToLookFor))
190                 return i;
191     }
192     else
193     {
194         for (; i < numElements; ++i)
195             if (stringToLookFor == strings.getReference (i))
196                 return i;
197     }
198 
199     return -1;
200 }
201 
202 //==============================================================================
remove(const int index)203 void StringArray::remove (const int index)
204 {
205     strings.remove (index);
206 }
207 
removeString(StringRef stringToRemove,const bool ignoreCase)208 void StringArray::removeString (StringRef stringToRemove, const bool ignoreCase)
209 {
210     if (ignoreCase)
211     {
212         for (int i = size(); --i >= 0;)
213             if (strings.getReference(i).equalsIgnoreCase (stringToRemove))
214                 strings.remove (i);
215     }
216     else
217     {
218         for (int i = size(); --i >= 0;)
219             if (stringToRemove == strings.getReference (i))
220                 strings.remove (i);
221     }
222 }
223 
removeRange(int startIndex,int numberToRemove)224 void StringArray::removeRange (int startIndex, int numberToRemove)
225 {
226     strings.removeRange (startIndex, numberToRemove);
227 }
228 
229 //==============================================================================
removeEmptyStrings(const bool removeWhitespaceStrings)230 void StringArray::removeEmptyStrings (const bool removeWhitespaceStrings)
231 {
232     if (removeWhitespaceStrings)
233     {
234         for (int i = size(); --i >= 0;)
235             if (! strings.getReference(i).containsNonWhitespaceChars())
236                 strings.remove (i);
237     }
238     else
239     {
240         for (int i = size(); --i >= 0;)
241             if (strings.getReference(i).isEmpty())
242                 strings.remove (i);
243     }
244 }
245 
trim()246 void StringArray::trim()
247 {
248     for (int i = size(); --i >= 0;)
249     {
250         String& s = strings.getReference(i);
251         s = s.trim();
252     }
253 }
254 
255 //==============================================================================
256 struct InternalStringArrayComparator_CaseSensitive
257 {
compareElementswater::InternalStringArrayComparator_CaseSensitive258     static int compareElements (String& s1, String& s2) noexcept    { return s1.compare (s2); }
259 };
260 
261 struct InternalStringArrayComparator_CaseInsensitive
262 {
compareElementswater::InternalStringArrayComparator_CaseInsensitive263     static int compareElements (String& s1, String& s2) noexcept    { return s1.compareIgnoreCase (s2); }
264 };
265 
266 struct InternalStringArrayComparator_Natural
267 {
compareElementswater::InternalStringArrayComparator_Natural268     static int compareElements (String& s1, String& s2) noexcept    { return s1.compareNatural (s2); }
269 };
270 
sort(const bool ignoreCase)271 void StringArray::sort (const bool ignoreCase)
272 {
273     if (ignoreCase)
274     {
275         InternalStringArrayComparator_CaseInsensitive comp;
276         strings.sort (comp);
277     }
278     else
279     {
280         InternalStringArrayComparator_CaseSensitive comp;
281         strings.sort (comp);
282     }
283 }
284 
sortNatural()285 void StringArray::sortNatural()
286 {
287     InternalStringArrayComparator_Natural comp;
288     strings.sort (comp);
289 }
290 
291 //==============================================================================
joinIntoString(StringRef separator,int start,int numberToJoin) const292 String StringArray::joinIntoString (StringRef separator, int start, int numberToJoin) const
293 {
294     const int last = (numberToJoin < 0) ? size()
295                                         : jmin (size(), start + numberToJoin);
296 
297     if (start < 0)
298         start = 0;
299 
300     if (start >= last)
301         return String();
302 
303     if (start == last - 1)
304         return strings.getReference (start);
305 
306     const size_t separatorBytes = separator.text.sizeInBytes() - sizeof (String::CharPointerType::CharType);
307     size_t bytesNeeded = separatorBytes * (size_t) (last - start - 1);
308 
309     for (int i = start; i < last; ++i)
310         bytesNeeded += strings.getReference(i).getCharPointer().sizeInBytes() - sizeof (String::CharPointerType::CharType);
311 
312     String result;
313     result.preallocateBytes (bytesNeeded);
314 
315     String::CharPointerType dest (result.getCharPointer());
316 
317     while (start < last)
318     {
319         const String& s = strings.getReference (start);
320 
321         if (! s.isEmpty())
322             dest.writeAll (s.getCharPointer());
323 
324         if (++start < last && separatorBytes > 0)
325             dest.writeAll (separator.text);
326     }
327 
328     dest.writeNull();
329 
330     return result;
331 }
332 
addTokens(StringRef text,const bool preserveQuotedStrings)333 int StringArray::addTokens (StringRef text, const bool preserveQuotedStrings)
334 {
335     return addTokens (text, " \n\r\t", preserveQuotedStrings ? "\"" : "");
336 }
337 
addTokens(StringRef text,StringRef breakCharacters,StringRef quoteCharacters)338 int StringArray::addTokens (StringRef text, StringRef breakCharacters, StringRef quoteCharacters)
339 {
340     int num = 0;
341 
342     if (text.isNotEmpty())
343     {
344         for (String::CharPointerType t (text.text);;)
345         {
346             String::CharPointerType tokenEnd (CharacterFunctions::findEndOfToken (t,
347                                                                                   breakCharacters.text,
348                                                                                   quoteCharacters.text));
349             strings.add (String (t, tokenEnd));
350             ++num;
351 
352             if (tokenEnd.isEmpty())
353                 break;
354 
355             t = ++tokenEnd;
356         }
357     }
358 
359     return num;
360 }
361 
addLines(StringRef sourceText)362 int StringArray::addLines (StringRef sourceText)
363 {
364     int numLines = 0;
365     String::CharPointerType text (sourceText.text);
366     bool finished = text.isEmpty();
367 
368     while (! finished)
369     {
370         for (String::CharPointerType startOfLine (text);;)
371         {
372             const String::CharPointerType endOfLine (text);
373 
374             switch (text.getAndAdvance())
375             {
376                 case 0:     finished = true; break;
377                 case '\n':  break;
378                 case '\r':  if (*text == '\n') ++text; break;
379                 default:    continue;
380             }
381 
382             strings.add (String (startOfLine, endOfLine));
383             ++numLines;
384             break;
385         }
386     }
387 
388     return numLines;
389 }
390 
fromTokens(StringRef stringToTokenise,bool preserveQuotedStrings)391 StringArray StringArray::fromTokens (StringRef stringToTokenise, bool preserveQuotedStrings)
392 {
393     StringArray s;
394     s.addTokens (stringToTokenise, preserveQuotedStrings);
395     return s;
396 }
397 
fromTokens(StringRef stringToTokenise,StringRef breakCharacters,StringRef quoteCharacters)398 StringArray StringArray::fromTokens (StringRef stringToTokenise,
399                                      StringRef breakCharacters,
400                                      StringRef quoteCharacters)
401 {
402     StringArray s;
403     s.addTokens (stringToTokenise, breakCharacters, quoteCharacters);
404     return s;
405 }
406 
fromLines(StringRef stringToBreakUp)407 StringArray StringArray::fromLines (StringRef stringToBreakUp)
408 {
409     StringArray s;
410     s.addLines (stringToBreakUp);
411     return s;
412 }
413 
414 //==============================================================================
removeDuplicates(const bool ignoreCase)415 void StringArray::removeDuplicates (const bool ignoreCase)
416 {
417     for (int i = 0; i < size() - 1; ++i)
418     {
419         const String s (strings.getReference(i));
420 
421         for (int nextIndex = i + 1;;)
422         {
423             nextIndex = indexOf (s, ignoreCase, nextIndex);
424 
425             if (nextIndex < 0)
426                 break;
427 
428             strings.remove (nextIndex);
429         }
430     }
431 }
432 
appendNumbersToDuplicates(const bool ignoreCase,const bool appendNumberToFirstInstance,CharPointer_UTF8 preNumberString,CharPointer_UTF8 postNumberString)433 void StringArray::appendNumbersToDuplicates (const bool ignoreCase,
434                                              const bool appendNumberToFirstInstance,
435                                              CharPointer_UTF8 preNumberString,
436                                              CharPointer_UTF8 postNumberString)
437 {
438     CharPointer_UTF8 defaultPre (" ("), defaultPost (")");
439 
440     if (preNumberString.getAddress() == nullptr)
441         preNumberString = defaultPre;
442 
443     if (postNumberString.getAddress() == nullptr)
444         postNumberString = defaultPost;
445 
446     for (int i = 0; i < size() - 1; ++i)
447     {
448         String& s = strings.getReference(i);
449 
450         int nextIndex = indexOf (s, ignoreCase, i + 1);
451 
452         if (nextIndex >= 0)
453         {
454             const String original (s);
455 
456             int number = 0;
457 
458             if (appendNumberToFirstInstance)
459                 s = original + String (preNumberString) + String (++number) + String (postNumberString);
460             else
461                 ++number;
462 
463             while (nextIndex >= 0)
464             {
465                 set (nextIndex, (*this)[nextIndex] + String (preNumberString) + String (++number) + String (postNumberString));
466                 nextIndex = indexOf (original, ignoreCase, nextIndex + 1);
467             }
468         }
469     }
470 }
471 
ensureStorageAllocated(int minNumElements)472 void StringArray::ensureStorageAllocated (int minNumElements)
473 {
474     strings.ensureStorageAllocated (minNumElements);
475 }
476 
minimiseStorageOverheads()477 void StringArray::minimiseStorageOverheads()
478 {
479     strings.minimiseStorageOverheads();
480 }
481 
482 }
483