1 /** 2 Defines a string based multi-map with conserved insertion order. 3 4 Copyright: © 2012-2014 Sönke Ludwig 5 License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 6 Authors: Sönke Ludwig 7 */ 8 module vibe.utils.dictionarylist; 9 10 import vibe.utils.string : icmp2; 11 import std.exception : enforce; 12 13 14 /** 15 Behaves similar to $(D VALUE[string]) but the insertion order is not changed 16 and multiple values per key are supported. 17 18 This kind of map is used for MIME headers (e.g. for HTTP, see 19 vibe.inet.message.InetHeaderMap), or for form data 20 (vibe.inet.webform.FormFields). Note that the map can contain fields with 21 the same key multiple times if addField is used for insertion. Insertion 22 order is preserved. 23 24 Note that despite case not being relevant for matching keyse, iterating 25 over the map will yield the original case of the key that was put in. 26 27 Insertion and lookup has O(n) complexity. 28 */ 29 struct DictionaryList(VALUE, bool case_sensitive = true, size_t NUM_STATIC_FIELDS = 32, bool USE_HASHSUM = false) { 30 import std.typecons : Tuple; 31 32 private { 33 alias KeyValue = Tuple!(string, "key", ValueType, "value"); 34 35 static struct Field { 36 static if (USE_HASHSUM) uint keyCheckSum; 37 else { 38 enum keyCheckSum = 0; 39 this(uint, string key, VALUE value) { this.key = key; this.value = value; } 40 } 41 KeyValue tuple; 42 @property ref inout(string) key() inout { return tuple.key; } 43 @property ref inout(VALUE) value() inout { return tuple.value; } 44 } 45 Field[NUM_STATIC_FIELDS] m_fields; 46 size_t m_fieldCount = 0; 47 Field[] m_extendedFields; 48 49 enum bool safeValueCopy = __traits(compiles, (VALUE v) @safe { VALUE vc; vc = v; }); 50 template typedGet(T) { 51 enum typedGet = __traits(compiles, (VALUE v) { return v.get!T; }); 52 } 53 template canAssign(T) { 54 enum canAssign = __traits(compiles, (T t) { VALUE v = t; }); 55 } 56 } 57 58 alias ValueType = VALUE; 59 60 struct FieldTuple { string key; ValueType value; } 61 62 /** The number of fields present in the map. 63 */ 64 @property size_t length() const { return m_fieldCount + m_extendedFields.length; } 65 66 /// Supports serialization using vibe.data.serialization. 67 static DictionaryList fromRepresentation(FieldTuple[] array) 68 { 69 DictionaryList ret; 70 foreach (ref v; array) ret.addField(v.key, v.value); 71 return ret; 72 } 73 /// ditto 74 FieldTuple[] toRepresentation() 75 const { 76 auto ret = new FieldTuple[](this.length); 77 size_t i = 0; 78 foreach (k, ref v; this.byKeyValue) { 79 // avoid bogus warning: 80 // scope variable `k` assigned to non-scope `ret[i++]` 81 // note that k is of type string and the bytes referred to by the 82 // string should not be annotated scope anywhere 83 () @trusted { 84 ret[i++] = FieldTuple(k, v); 85 } (); 86 } 87 return ret; 88 } 89 90 /** Generates an associative-array equivalent string representation of the dictionary. 91 */ 92 void toString(scope void delegate(const(char)[] str) @safe sink) 93 const { 94 sink("["); 95 bool first = true; 96 97 foreach (k, v; this.byKeyValue) { 98 if (!first) sink(", "); 99 else first = false; 100 () @trusted { 101 import std.format : formattedWrite; 102 string[] ka = (&k)[0 .. 1]; 103 ValueType[] va = (&v)[0 .. 1]; 104 sink.formattedWrite("%(%s%): %(%s%)", ka, va); 105 } (); 106 } 107 sink("]"); 108 } 109 /// ditto 110 void toString(scope void delegate(const(char)[] str) @system sink) 111 const { 112 toString(cast(void delegate(const(char)[]) @safe)sink); 113 } 114 /// ditto 115 string toString() 116 const { 117 import std.array : appender; 118 auto ret = appender!string(); 119 toString((s) { ret.put(s); }); 120 return ret.data; 121 } 122 123 /** Removes the first field that matches the given key. 124 */ 125 void remove(string key) 126 { 127 static if (USE_HASHSUM) auto keysum = computeCheckSumI(key); 128 enum keysum = 0; 129 auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum); 130 if( idx >= 0 ){ 131 auto slice = m_fields[0 .. m_fieldCount]; 132 removeFromArrayIdx(slice, idx); 133 m_fieldCount--; 134 } else { 135 idx = getIndex(m_extendedFields, key, keysum); 136 enforce(idx >= 0); 137 removeFromArrayIdx(m_extendedFields, idx); 138 } 139 } 140 141 /** Removes all fields that matches the given key. 142 */ 143 void removeAll(string key) 144 { 145 static if (USE_HASHSUM) auto keysum = computeCheckSumI(key); 146 else enum keysum = 0; 147 for (size_t i = 0; i < m_fieldCount;) { 148 if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key)) { 149 auto slice = m_fields[0 .. m_fieldCount]; 150 removeFromArrayIdx(slice, i); 151 m_fieldCount--; 152 } else i++; 153 } 154 155 for (size_t i = 0; i < m_extendedFields.length;) { 156 if (m_fields[i].keyCheckSum == keysum && matches(m_fields[i].key, key)) 157 removeFromArrayIdx(m_extendedFields, i); 158 else i++; 159 } 160 } 161 162 /// Used by `remove` and `removeAll` 163 private static void removeFromArrayIdx(ref Field[] array, size_t idx) 164 { 165 foreach( j; idx+1 .. array.length) 166 array[j-1] = array[j]; 167 array.length = array.length-1; 168 } 169 170 /** Adds a new field to the map. 171 172 The new field will be added regardless of any existing fields that 173 have the same key, possibly resulting in duplicates. Use opIndexAssign 174 if you want to avoid duplicates. 175 */ 176 void addField(string key, ValueType value) 177 { 178 static if (USE_HASHSUM) auto keysum = computeCheckSumI(key); 179 else enum keysum = 0; 180 if (m_fieldCount < m_fields.length) 181 m_fields[m_fieldCount++] = Field(keysum, key, value); 182 else m_extendedFields ~= Field(keysum, key, value); 183 } 184 185 void addField(T)(string key, T value) 186 if (canAssign!T) 187 { 188 ValueType convertedValue = value; 189 addField(key, convertedValue); 190 } 191 192 /** Returns the first field that matches the given key. 193 194 If no field is found, def_val is returned. 195 */ 196 inout(ValueType) get(string key, lazy inout(ValueType) def_val = ValueType.init) 197 inout { 198 if (auto pv = key in this) return *pv; 199 return def_val; 200 } 201 202 // DMD bug: cannot set T.init as default value for def_val parameter, 203 // because compilation fails with message: 204 // Error: undefined identifier 'T' 205 /// ditto 206 inout(T) get(T)(string key, lazy inout(T) def_val) 207 inout if (typedGet!T) { 208 if (auto pv = key in this) return (*pv).get!T; 209 return def_val; 210 } 211 212 /// ditto 213 inout(T) get(T)(string key) // Work around DMD bug 214 inout if (typedGet!T) { 215 return get!T(key, inout(T).init); 216 } 217 218 /** Returns all values matching the given key. 219 220 Note that the version returning an array will allocate for each call. 221 */ 222 const(ValueType)[] getAll(string key) 223 const @trusted { // appender 224 import std.array; 225 auto ret = appender!(const(ValueType)[])(); 226 getAll(key, (v) @trusted { ret.put(v); }); 227 return ret.data; 228 } 229 /// ditto 230 void getAll(string key, scope void delegate(const(ValueType)) @safe nothrow del) 231 const { 232 getAllImpl(key, del); 233 } 234 /// ditto 235 void getAll(string key, scope void delegate(const(ValueType)) @safe del) 236 const { 237 getAllImpl(key, del); 238 } 239 240 private void getAllImpl(C)(string key, scope C del) 241 const { 242 static if (USE_HASHSUM) uint keysum = computeCheckSumI(key); 243 else enum keysum = 0; 244 foreach (ref f; m_fields[0 .. m_fieldCount]) { 245 static if (USE_HASHSUM) 246 if (f.keyCheckSum != keysum) continue; 247 if (matches(f.key, key)) del(f.value); 248 } 249 foreach (ref f; m_extendedFields) { 250 static if (USE_HASHSUM) 251 if (f.keyCheckSum != keysum) continue; 252 if (matches(f.key, key)) del(f.value); 253 } 254 } 255 256 /** Returns the first value matching the given key. 257 */ 258 inout(ValueType) opIndex(string key) 259 inout { 260 auto pitm = key in this; 261 enforce(pitm !is null, "Accessing non-existent key '"~key~"'."); 262 return *pitm; 263 } 264 265 /** Adds or replaces the given field with a new value. 266 */ 267 ValueType opIndexAssign(ValueType val, string key) 268 { 269 static if (USE_HASHSUM) auto keysum = computeCheckSumI(key); 270 else enum keysum = 0; 271 auto pitm = key in this; 272 if (pitm) *pitm = val; 273 else if (m_fieldCount < m_fields.length) m_fields[m_fieldCount++] = Field(keysum, key, val); 274 else m_extendedFields ~= Field(keysum, key, val); 275 return val; 276 } 277 278 /// ditto 279 ValueType opIndexAssign(T)(T val, string key) 280 if (canAssign!T) 281 { 282 ValueType convertedVal = val; 283 return opIndexAssign(convertedVal, key); 284 } 285 286 /** Returns a pointer to the first field that matches the given key. 287 */ 288 inout(ValueType)* opBinaryRight(string op)(string key) inout if(op == "in") 289 { 290 static if (USE_HASHSUM) uint keysum = computeCheckSumI(key); 291 else enum keysum = 0; 292 auto idx = getIndex(m_fields[0 .. m_fieldCount], key, keysum); 293 if (idx >= 0) return &m_fields[idx].tuple[1]; 294 idx = getIndex(m_extendedFields, key, keysum); 295 if (idx >= 0) return &m_extendedFields[idx].tuple[1]; 296 return null; 297 } 298 /// ditto 299 bool opBinaryRight(string op)(string key) inout if(op == "!in") { 300 return !(key in this); 301 } 302 303 /** Iterates over all fields, including duplicates. 304 */ 305 auto byKeyValue() {return Rng!false(&this, 0); } 306 /// ditto 307 auto byKeyValue() const { return Rng!true(&this, 0); } 308 /// ditto 309 auto byKey() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[0]); } 310 /// ditto 311 auto byValue() inout { import std.algorithm.iteration : map; return byKeyValue().map!(p => p[1]); } 312 313 static if (is(typeof({ const(ValueType) v; ValueType w; w = v; }))) { 314 /** Duplicates the header map. 315 */ 316 @property DictionaryList dup() 317 const { 318 DictionaryList ret; 319 ret.m_fields[0 .. m_fieldCount] = m_fields[0 .. m_fieldCount]; 320 ret.m_fieldCount = m_fieldCount; 321 ret.m_extendedFields = m_extendedFields.dup; 322 return ret; 323 } 324 } 325 326 private ptrdiff_t getIndex(in Field[] map, string key, uint keysum) 327 const { 328 foreach (i, ref const(Field) entry; map) { 329 static if (USE_HASHSUM) if (entry.keyCheckSum != keysum) continue; 330 if (matches(entry.key, key)) return i; 331 } 332 return -1; 333 } 334 335 private static bool matches(string a, string b) 336 { 337 static if (case_sensitive) return a == b; 338 else return a.length == b.length && icmp2(a, b) == 0; 339 } 340 341 // very simple check sum function with a good chance to match 342 // strings with different case equal 343 static if (USE_HASHSUM) private static uint computeCheckSumI(string s) 344 @trusted { 345 uint csum = 0; 346 immutable(char)* pc = s.ptr, pe = s.ptr + s.length; 347 for (; pc != pe; pc++) { 348 static if (case_sensitive) csum ^= *pc; 349 else csum ^= *pc & 0x1101_1111; 350 csum = (csum << 1) | (csum >> 31); 351 } 352 return csum; 353 } 354 355 private static struct Rng(bool CONST) { 356 @safe nothrow @nogc: 357 static if (CONST) { 358 alias KVT = const(KeyValue); 359 const(DictionaryList)* list; 360 } else { 361 alias KVT = KeyValue; 362 DictionaryList* list; 363 } 364 size_t idx; 365 366 @property Rng save() { return this; } 367 368 @property bool empty() const { return idx >= list.length; } 369 @property ref KVT front() { 370 if (idx < list.m_fieldCount) 371 return list.m_fields[idx].tuple; 372 return list.m_extendedFields[idx - list.m_fieldCount].tuple; 373 } 374 void popFront() { idx++; } 375 } 376 } 377 378 @safe unittest 379 { 380 assert(DictionaryList!(string, true, 2).safeValueCopy); 381 } 382 383 @safe unittest { 384 DictionaryList!(int, true) a; 385 a.addField("a", 1); 386 a.addField("a", 2); 387 assert(a["a"] == 1); 388 assert(a.getAll("a") == [1, 2]); 389 a["a"] = 3; 390 assert(a["a"] == 3); 391 assert(a.getAll("a") == [3, 2]); 392 a.removeAll("a"); 393 assert(a.getAll("a").length == 0); 394 assert(a.get("a", 4) == 4); 395 a.addField("b", 2); 396 a.addField("b", 1); 397 a.remove("b"); 398 assert(a.getAll("b") == [1]); 399 400 DictionaryList!(int, false) b; 401 b.addField("a", 1); 402 b.addField("A", 2); 403 assert(b["A"] == 1); 404 assert(b.getAll("a") == [1, 2]); 405 } 406 407 @safe nothrow unittest { 408 DictionaryList!(int, true) a; 409 a.addField("a", 1); 410 a.addField("a", 2); 411 assert(a.getAll("a") == [1, 2]); 412 assert("a" in a); 413 a["a"] = 3; 414 assert(a.getAll("a") == [3, 2]); 415 a.removeAll("a"); 416 assert(a.getAll("a").length == 0); 417 } 418 419 unittest { 420 import std.variant : Variant; 421 DictionaryList!(Variant) c; 422 c["a"] = true; 423 c["b"] = "Hello"; 424 assert(c.get("a").type == typeid(bool)); 425 assert(c.get!string("b") == "Hello"); 426 assert(c.get!int("c") == int.init); 427 c.addField("d", 9); 428 c.addField("d", "bar"); 429 assert(c.getAll("d") == [ cast(Variant) 9, cast(Variant) "bar" ]); 430 } 431 432 @safe unittest { 433 import std.conv : text; 434 DictionaryList!int l; 435 l["foo"] = 42; 436 l["bar"] = 43; 437 assert(text(l) == `["foo": 42, "bar": 43]`, text(l)); 438 assert(l.toString() == `["foo": 42, "bar": 43]`, l.toString()); 439 } 440 441 // Issue 2004 442 unittest { 443 import std.variant : Variant; 444 DictionaryList!Variant l; 445 class C { 446 int x = 123; 447 } 448 l["foo"] = new C; 449 auto c = l.get!C("foo"); 450 assert(c.x == 123); 451 }