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