OpenShot Audio Library | OpenShotAudio  0.6.0
juce_StringPairArray.cpp
1 /*
2  ==============================================================================
3 
4  This file is part of the JUCE library.
5  Copyright (c) 2022 - Raw Material Software Limited
6 
7  JUCE is an open source library subject to commercial or open-source
8  licensing.
9 
10  The code included in this file is provided under the terms of the ISC license
11  http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12  To use, copy, modify, and/or distribute this software for any purpose with or
13  without fee is hereby granted provided that the above copyright notice and
14  this permission notice appear in all copies.
15 
16  JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17  EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18  DISCLAIMED.
19 
20  ==============================================================================
21 */
22 
23 namespace juce
24 {
25 
26 StringPairArray::StringPairArray (bool shouldIgnoreCase) : ignoreCase (shouldIgnoreCase)
27 {
28 }
29 
31  : keys (other.keys),
32  values (other.values),
33  ignoreCase (other.ignoreCase)
34 {
35 }
36 
38 {
39  keys = other.keys;
40  values = other.values;
41  return *this;
42 }
43 
45 {
46  auto num = size();
47 
48  if (num != other.size())
49  return false;
50 
51  for (int i = 0; i < num; ++i)
52  {
53  if (keys[i] == other.keys[i]) // optimise for the case where the keys are in the same order
54  {
55  if (values[i] != other.values[i])
56  return false;
57  }
58  else
59  {
60  // if we encounter keys that are in a different order, search remaining items by brute force..
61  for (int j = i; j < num; ++j)
62  {
63  auto otherIndex = other.keys.indexOf (keys[j], other.ignoreCase);
64 
65  if (otherIndex < 0 || values[j] != other.values[otherIndex])
66  return false;
67  }
68 
69  return true;
70  }
71  }
72 
73  return true;
74 }
75 
77 {
78  return ! operator== (other);
79 }
80 
82 {
83  return values[keys.indexOf (key, ignoreCase)];
84 }
85 
86 String StringPairArray::getValue (StringRef key, const String& defaultReturnValue) const
87 {
88  auto i = keys.indexOf (key, ignoreCase);
89 
90  if (i >= 0)
91  return values[i];
92 
93  return defaultReturnValue;
94 }
95 
96 bool StringPairArray::containsKey (StringRef key) const noexcept
97 {
98  return keys.contains (key, ignoreCase);
99 }
100 
101 void StringPairArray::set (const String& key, const String& value)
102 {
103  auto i = keys.indexOf (key, ignoreCase);
104 
105  if (i >= 0)
106  {
107  values.set (i, value);
108  }
109  else
110  {
111  keys.add (key);
112  values.add (value);
113  }
114 }
115 
117 {
118  for (int i = 0; i < other.size(); ++i)
119  set (other.keys[i], other.values[i]);
120 }
121 
123 {
124  keys.clear();
125  values.clear();
126 }
127 
129 {
130  remove (keys.indexOf (key, ignoreCase));
131 }
132 
133 void StringPairArray::remove (int index)
134 {
135  keys.remove (index);
136  values.remove (index);
137 }
138 
139 void StringPairArray::setIgnoresCase (bool shouldIgnoreCase)
140 {
141  ignoreCase = shouldIgnoreCase;
142 }
143 
144 bool StringPairArray::getIgnoresCase() const noexcept
145 {
146  return ignoreCase;
147 }
148 
150 {
151  String s;
152 
153  for (int i = 0; i < keys.size(); ++i)
154  {
155  s << keys[i] << " = " << values[i];
156 
157  if (i < keys.size())
158  s << ", ";
159  }
160 
161  return s;
162 }
163 
165 {
167  values.minimiseStorageOverheads();
168 }
169 
170 template <typename Map>
171 void StringPairArray::addMapImpl (const Map& toAdd)
172 {
173  // If we just called `set` for each item in `toAdd`, that would
174  // perform badly when adding to large StringPairArrays, as `set`
175  // has to loop through the whole container looking for matching keys.
176  // Instead, we use a temporary map to give us better lookup performance.
177  std::map<String, int> contents;
178 
179  const auto normaliseKey = [this] (const String& key)
180  {
181  return ignoreCase ? key.toLowerCase() : key;
182  };
183 
184  for (auto i = 0; i != size(); ++i)
185  contents.emplace (normaliseKey (getAllKeys().getReference (i)), i);
186 
187  for (const auto& pair : toAdd)
188  {
189  const auto key = normaliseKey (pair.first);
190  const auto it = contents.find (key);
191 
192  if (it != contents.cend())
193  {
194  values.getReference (it->second) = pair.second;
195  }
196  else
197  {
198  contents.emplace (key, static_cast<int> (contents.size()));
199  keys.add (pair.first);
200  values.add (pair.second);
201  }
202  }
203 }
204 
205 void StringPairArray::addUnorderedMap (const std::unordered_map<String, String>& toAdd) { addMapImpl (toAdd); }
206 void StringPairArray::addMap (const std::map<String, String>& toAdd) { addMapImpl (toAdd); }
207 
208 //==============================================================================
209 //==============================================================================
210 #if JUCE_UNIT_TESTS
211 
212 static String operator""_S (const char* chars, size_t)
213 {
214  return String { chars };
215 }
216 
217 class StringPairArrayTests final : public UnitTest
218 {
219 public:
220  StringPairArrayTests()
221  : UnitTest ("StringPairArray", UnitTestCategories::text)
222  {}
223 
224  void runTest() override
225  {
226  beginTest ("addMap respects case sensitivity of StringPairArray");
227  {
228  StringPairArray insensitive { true };
229  insensitive.addMap ({ { "duplicate", "a" },
230  { "Duplicate", "b" } });
231 
232  expect (insensitive.size() == 1);
233  expectEquals (insensitive["DUPLICATE"], "a"_S);
234 
235  StringPairArray sensitive { false };
236  sensitive.addMap ({ { "duplicate", "a"_S },
237  { "Duplicate", "b"_S } });
238 
239  expect (sensitive.size() == 2);
240  expectEquals (sensitive["duplicate"], "a"_S);
241  expectEquals (sensitive["Duplicate"], "b"_S);
242  expectEquals (sensitive["DUPLICATE"], ""_S);
243  }
244 
245  beginTest ("addMap overwrites existing pairs");
246  {
247  StringPairArray insensitive { true };
248  insensitive.set ("key", "value");
249  insensitive.addMap ({ { "KEY", "VALUE" } });
250 
251  expect (insensitive.size() == 1);
252  expectEquals (insensitive.getAllKeys()[0], "key"_S);
253  expectEquals (insensitive.getAllValues()[0], "VALUE"_S);
254 
255  StringPairArray sensitive { false };
256  sensitive.set ("key", "value");
257  sensitive.addMap ({ { "KEY", "VALUE" },
258  { "key", "another value" } });
259 
260  expect (sensitive.size() == 2);
261  expect (sensitive.getAllKeys() == StringArray { "key", "KEY" });
262  expect (sensitive.getAllValues() == StringArray { "another value", "VALUE" });
263  }
264 
265  beginTest ("addMap doesn't change the order of existing keys");
266  {
267  StringPairArray array;
268  array.set ("a", "a");
269  array.set ("z", "z");
270  array.set ("b", "b");
271  array.set ("y", "y");
272  array.set ("c", "c");
273 
274  array.addMap ({ { "B", "B" },
275  { "0", "0" },
276  { "Z", "Z" } });
277 
278  expect (array.getAllKeys() == StringArray { "a", "z", "b", "y", "c", "0" });
279  expect (array.getAllValues() == StringArray { "a", "Z", "B", "y", "c", "0" });
280  }
281 
282  beginTest ("addMap has equivalent behaviour to addArray");
283  {
284  StringPairArray initial;
285  initial.set ("aaa", "aaa");
286  initial.set ("zzz", "zzz");
287  initial.set ("bbb", "bbb");
288 
289  auto withAddMap = initial;
290  withAddMap.addMap ({ { "ZZZ", "ZZZ" },
291  { "ddd", "ddd" } });
292 
293  auto withAddArray = initial;
294  withAddArray.addArray ([]
295  {
296  StringPairArray toAdd;
297  toAdd.set ("ZZZ", "ZZZ");
298  toAdd.set ("ddd", "ddd");
299  return toAdd;
300  }());
301 
302  expect (withAddMap == withAddArray);
303  }
304  }
305 };
306 
307 static StringPairArrayTests stringPairArrayTests;
308 
309 #endif
310 
311 } // namespace juce
String & getReference(int index) noexcept
int indexOf(StringRef stringToLookFor, bool ignoreCase=false, int startIndex=0) const
int size() const noexcept
void add(String stringToAdd)
void set(int index, String newString)
void remove(int index)
void setIgnoresCase(bool shouldIgnoreCase)
String getValue(StringRef, const String &defaultReturnValue) const
StringPairArray & operator=(const StringPairArray &other)
bool containsKey(StringRef key) const noexcept
void set(const String &key, const String &value)
void remove(StringRef key)
StringPairArray(bool ignoreCaseWhenComparingKeys=true)
bool getIgnoresCase() const noexcept
const StringArray & getAllKeys() const noexcept
const String & operator[](StringRef key) const
bool operator!=(const StringPairArray &other) const
void addMap(const std::map< String, String > &mapToAdd)
void addArray(const StringPairArray &other)
void addUnorderedMap(const std::unordered_map< String, String > &mapToAdd)
int size() const noexcept
bool operator==(const StringPairArray &other) const